0%

【重構系列】(二) DI與IOC

SourceCode : https://github.com/toyo0103/Demo_EditTemplate_1

依據前一篇延續,接著我們把焦點放在Application與Service之間的狀況

[![](https://3.bp.blogspot.com/-urWNSzGdlp8/V7O6-tD1woI/AAAAAAAAH6w/z2GwsqBn9hANkslSqNpALNAuRiBIuuI4ACLcB/s400/1.png)](https://3.bp.blogspot.com/-urWNSzGdlp8/V7O6-tD1woI/AAAAAAAAH6w/z2GwsqBn9hANkslSqNpALNAuRiBIuuI4ACLcB/s1600/1.png)
前一篇有提到程式應該要面對**抽象**避免高耦合的情況發生,改一髮進而動全身,所以有用**介面**的方式去處理
[![](https://3.bp.blogspot.com/-O7kXIwvgtDk/V7O7kOH1D0I/AAAAAAAAH60/M49OysRZ7R4LxOLv4UNE9WJVLBBpsICJwCLcB/s400/1.png)](https://3.bp.blogspot.com/-O7kXIwvgtDk/V7O7kOH1D0I/AAAAAAAAH60/M49OysRZ7R4LxOLv4UNE9WJVLBBpsICJwCLcB/s1600/1.png)

然而來看看目前實際的程式狀況 : 直接耦合

[![](https://4.bp.blogspot.com/-csgLbuLY5Dw/V7O9BEEADYI/AAAAAAAAH7A/n6mzb9hYUiksdAHYuQmBXuH6aPO0Sk8-wCLcB/s400/1.png)](https://4.bp.blogspot.com/-csgLbuLY5Dw/V7O9BEEADYI/AAAAAAAAH7A/n6mzb9hYUiksdAHYuQmBXuH6aPO0Sk8-wCLcB/s1600/1.png)
[![](https://2.bp.blogspot.com/-LkVwTAXMQHc/V7O9TcpERgI/AAAAAAAAH7I/18cwMlw6YtQxVKhppexI25lzS--gxIIagCLcB/s400/1.png)](https://2.bp.blogspot.com/-LkVwTAXMQHc/V7O9TcpERgI/AAAAAAAAH7I/18cwMlw6YtQxVKhppexI25lzS--gxIIagCLcB/s1600/1.png)

這邊會談到兩個用來解開這種狀況的觀念

  • 依賴注入(Dependency Injection)簡稱DI
  • **控制反轉(**Inversion Of Control)簡稱IOC

先看看Google怎麼解釋
控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI)

不知道看完有沒有懂的感覺XD,明明都是中文看完卻很想說「先生(小姐)可以講中文嗎?」。
撇開這些專業的術語不看,我們直接開始重構來體會這兩個概念

一、重構開始

抽取介面
避免直接耦合的第一步,抽取介面來隔離實體,既然這邊實際耦合了POIService,那我們就為它抽取出Interface IPOIService

[![](https://3.bp.blogspot.com/-LrWsJfLH2vU/V7PAEPoqZKI/AAAAAAAAH7Y/VCUD_9YVxnEPfAzFCKpQvHY3jQRkMBD9ACLcB/s1600/1.png)](https://3.bp.blogspot.com/-LrWsJfLH2vU/V7PAEPoqZKI/AAAAAAAAH7Y/VCUD_9YVxnEPfAzFCKpQvHY3jQRkMBD9ACLcB/s1600/1.png)
1
2
3
4
5
6
7
8
9
10
public interface IPOIService
{
/// <summary>
/// 取得屬於頻道的POI資料
/// </summary>
/// <param name="channelID">The channel identifier.</param>
/// <returns>List&lt;POI&gt;.</returns>
List<POI> Get(string channelID);
}

POIService掛上IPOIService

[![](https://2.bp.blogspot.com/-YS5Dd3-SnX4/V7PA1QAh0ZI/AAAAAAAAH7g/Av6PbiG5yYgct0SYM1kP_n8mTQTLy0N2ACLcB/s400/1.png)](https://2.bp.blogspot.com/-YS5Dd3-SnX4/V7PA1QAh0ZI/AAAAAAAAH7g/Av6PbiG5yYgct0SYM1kP_n8mTQTLy0N2ACLcB/s1600/1.png)
**讓Application依賴介面**

上一篇遇到這種狀況時,我們會做一個Factory來產生實體,並且封裝在Service層。這次我們不這麼做,改成用依賴注入(DI)的方式來實作,將IPOIService改成用建構子帶入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HomeController : Controller
{
IPOIService _POIService;
public HomeController(IPOIService poiService)
{
//POIService改成依賴介面IPOIService
//並且實體是由外部來決定,也就是所謂的依賴注入DI
_POIService = poiService;
}

public ActionResult Index(string id)
{
//依賴外部注入的IPOIService介面
var POIs = _POIService.Get(id);
return Json(POIs,JsonRequestBehavior.AllowGet);
}
}

這邊用的IPOIService是由外部注入的,實體已經不由HomeController 內部來決定了(不New實體了),也就是上面所提到的DI 依賴注入(要用的實體由建構子注入),IOC控制反轉(實體已經由內部控制改由外部控制了)

接下來面臨的問題是,雖然我們讓實體改成用外部注入的方式,但HomeController在建立的時候是由系統去產生的,它怎麼知道IPOIService的實體是要注入誰,所以現在執行程式會看到以下結果

[![](https://4.bp.blogspot.com/-0ijdWklCbSk/V7QYVfZnz_I/AAAAAAAAH7w/Bj3Sa8cECEcTJNOsObhzWMd6vMjwUJIggCLcB/s1600/1.png)](https://4.bp.blogspot.com/-0ijdWklCbSk/V7QYVfZnz_I/AAAAAAAAH7w/Bj3Sa8cECEcTJNOsObhzWMd6vMjwUJIggCLcB/s1600/1.png)

這時候就要介紹Unity這個套件來幫我們解決這個問題了

**


**何謂Unity

[![](https://1.bp.blogspot.com/-PEXphTQycyE/V7QY_Y7zGoI/AAAAAAAAH70/n8FT2stz8PQnOEXWrUSl95ilLNBrmTz1gCLcB/s400/MSUNITY.jpg)](https://1.bp.blogspot.com/-PEXphTQycyE/V7QY_Y7zGoI/AAAAAAAAH70/n8FT2stz8PQnOEXWrUSl95ilLNBrmTz1gCLcB/s1600/MSUNITY.jpg)
MSDN上的Unity套件介紹 : [https://msdn.microsoft.com/en-us/library/ff647202.aspx](https://msdn.microsoft.com/en-us/library/ff647202.aspx) ** **其實這個套件說穿了就是在Application啟動時,註冊每個Interface對應的實體是誰,當碰到要注入這個Interface時,Unity就會去找找看你有沒有跟它說對應的實體,如果有,它就把實體產生出來幫你帶入。那就一步一步做做看邊體會它的意思 **1.安裝Unity套件 ** 請在Application專案透過Nuget搜尋Unity安裝**Unity.Mvc**
[![](https://2.bp.blogspot.com/-HhIfl_RkSKA/V7QbZyR_3SI/AAAAAAAAH8M/C4VxtKVrLdUkdbq6eGIXxg0bH9m_A6DpACLcB/s640/1.png)](https://2.bp.blogspot.com/-HhIfl_RkSKA/V7QbZyR_3SI/AAAAAAAAH8M/C4VxtKVrLdUkdbq6eGIXxg0bH9m_A6DpACLcB/s1600/1.png)
** ****2.註冊Interface應該對應的實體** ** **這時候打開App_Start資料夾應該會看到多了一個檔案**UnityConfig.cs**
[![](https://2.bp.blogspot.com/-MCmsg-5dEEY/V7Qb_xiRLFI/AAAAAAAAH8U/3JMMEHww4joUlfQI3HjpW0abcn343O9KwCLcB/s320/1.png)](https://2.bp.blogspot.com/-MCmsg-5dEEY/V7Qb_xiRLFI/AAAAAAAAH8U/3JMMEHww4joUlfQI3HjpW0abcn343O9KwCLcB/s1600/1.png)
打開它會看到以下Code跟註解,教你怎麼使用它
[![](https://2.bp.blogspot.com/-B5FxVSeEW9Q/V7QcPsxDVeI/AAAAAAAAH8Y/lsWGO864ftoAVFsUH3nmO9mB4LL0lOAZwCLcB/s640/1.png)](https://2.bp.blogspot.com/-B5FxVSeEW9Q/V7QcPsxDVeI/AAAAAAAAH8Y/lsWGO864ftoAVFsUH3nmO9mB4LL0lOAZwCLcB/s1600/1.png)
請將**RegisterTypes** 改成以下
```csharp /// Registers the type mappings with the Unity container. /// The unity container to configure. /// There is no need to register concrete types such as controllers or API controllers (unless you want to /// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered. public static void RegisterTypes(IUnityContainer container) {
        //TransientLifetimeManager為這個實體的創建生命週期
        //表示執行程式時,每次碰到IPOIService都會去new 一個新的POIService
        //Unity有許多生命週期可以使用,可以參考官方的文件
        //例如PerResolveLifetimeManager就是本次Request只有New一次實體,如果同個Request碰到多次
        //都還是會返回第一次New的實體,直到Reqeust結束才會被消滅掉,Singleton的概念
        container.RegisterType<IPOIService, POIService>(new TransientLifetimeManager());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


關於Unity創建實體的生命週期請參考官方文件 :&nbsp;[https://msdn.microsoft.com/en-us/library/ff660872(v=pandp.20).aspx](https://msdn.microsoft.com/en-us/library/ff660872(v=pandp.20).aspx)

註冊好後接著執行看看,會發現程式又活過來了,不會再出現剛剛的錯誤頁面,因為程式已經知道該Interface對應的實體是誰,當由外部注入時,Unity會自己去幫你控制實體的產生並且注入

<div class="separator" style="clear: both; text-align: center;">[![](https://4.bp.blogspot.com/-QgBTpClZA_g/V7QgW_P_VNI/AAAAAAAAH8o/EBBv1BKh0pIIbOKaZoPRQB4Gt-dmnLcmgCLcB/s640/1.png)](https://4.bp.blogspot.com/-QgBTpClZA_g/V7QgW_P_VNI/AAAAAAAAH8o/EBBv1BKh0pIIbOKaZoPRQB4Gt-dmnLcmgCLcB/s1600/1.png)</div>

透過這個重構的實例,我們可以從裡面瞭解到何謂**DI**與**IOC**的概念,但可能會想說,那這樣的好處是什麼? 直接New錯了嗎?

回到前一篇提到的,如果直接New的話也就是高耦合的狀況,如果今天面對的實體改動時,很難避免耦合的地方不會跟著變動,所以我們**依賴抽象**(Interface),用了Unity後其實又牽扯到一個大重點"**職責分離**"。

以後如果有Interface與實體的對應關係,直接就會想到要去**UnityConfig.cs**來改,所有的註冊表在這邊統一管理而且一目了然,如果哪天主管跟你說**POIService**它不喜歡,要重新寫個**NewPOIService**,那我們是不是直接把**NewPOIService實作**<span style="color: #bf9000;">IPOIService</span>後,然後打開**UnityConfig.cs**改成這樣即可

```csharp
container.RegisterType<IPOIService, NewPOIService>(new TransientLifetimeManager());

其他地方連動都不用動,就已經直接將對應的實體給換掉了!!! 統一管理之外,也能達到最小更動原則。

這次的重構就先到這邊~