Unity 入門 (4)

上回,把 Unity 容器解析型別的部分介紹完。

預設對應

先前範例中呼叫的 RegisterType 方法只帶兩個參數,像這樣:

container.RegisterType<ISayHello, SayHelloInChinese>();

此版本的方法宣告已經在上一篇提過了,兩個泛型參數分別是 TFrom 和 TTo,代表要從哪個型別對應至哪個目標型別。

其實 RegisterType 還有另一個多帶了一個字串參數的版本,參數名稱叫做「name」。藉由此參數,我們可以為該次呼叫所建立的對應關係取個名稱,以便日後以該名稱來明白指定要使用哪一個對應關係。那麼,先前範例中使用的版本,也就是沒有指定名稱的那個版本所建立的對應關係就叫做「預設對應」。換言之,預設對應關係指定了某抽象型別所預設對應的具象型別(或者說,某介面所預設對應的實作類別)。

現在,我們可以將上一個小節所提的問題稍微修飾一下:若在程式中註冊了兩次或多次的預設對應(如以下程式片段),Unity 在建立物件時會使用哪一個?

        container.RegisterType<ISayHello, SayHelloInChinese>();
        container.RegisterType<ISayHello, SayHelloInEnglish>();

答案已在上回揭曉:Unity 會使用 SayHelloInEnglish。因為....

如果對同一個抽象型別指定了多個預設對應的具象型別,Unity 會使用最後一個對應。

也就是說,針對同一個抽象型別,後來設定的對應會蓋掉先前的設定。為了方便說明,姑且稱之為「後來居上原則」。

明確指定預設對應

如果你需要對同一個介面設定多個對應的類別,同時又希望讓第一次設定的預設對應型別不被後來的設定蓋掉,則可以這麼寫:

        container.RegisterType<ISayHello, SayHelloInChinese>();
        container.RegisterType<ISayHello, SayHelloInEnglish>(“eng”);

第二行在呼叫 RegisterType 方法時額外傳入了一個字串參數,就是先前提過的那個「name」參數。底下是這個版本的 RegisterType 方法的原型宣告:

IUnityContainer RegisterType(
 Type from,
 Type to,
 string name,
 params InjectionMember[] injectionMembers
)

如先前所說,參數 name 可讓你為這次註冊的對應關係取個名稱。如此一來,先前所設定的對應關係(ISayHello 對應至 SayHelloInChinese)就仍然是預設對應,而在解析物件時,你還可以利用這個自訂名稱來明白指定欲使用哪一個對應關係。例如:

        container.RegisterType<ISayHello, SayHelloInChinese>();
        container.RegisterType<ISayHello, SayHelloInEnglish>(“eng”);

        ISayHello hello1 = container.Resolve<ISayHello>();
        ISayHello hello2= container.Resolve<ISayHello>("eng");

其中 hello1 的實際型別會是 SayHelloInChinese,因為我們在呼叫 Resolve 方法時並沒有指定要使用哪個對應關係,於是就會使用預設對應。hello2 的實際型別則是 SayHelloInEnglish,因為在呼叫 Resolve 方法時有傳入 name 參數,告訴 Unity 我們要使用先前註冊為 “eng” 這個名稱的對應關係。

(呼~好像講太細了....)

自動註冊

一個介面可能有很多實作類別,而有時候,實作類別的數量可能多達十幾個。碰到這種情況,如果照前面範例的寫法,每一個對應關係就得呼叫一次 RegisterType 方法,結果就會產生一堆長得很像的程式碼──既不好看,維護起來也不是那麼方便。

如果 DI 容器能夠自動註冊某個組件裡面所有實作特定介面(例如 ISayHello)的類別,那就簡潔省力多了。可惜 Unity 並未提供自動註冊的功能。

不過,我們還是可以利用 .NET 的反射機制(Reflection)來達到同樣效果。參考底下的程式片段:

foreach (var t in typeof(ISayHello).Assembly.GetExportedTypes())
{
    if (typeof(ISayHello).IsAssignableFrom(t))                
    {
        container.RegisterType(typeof(ISayHello), t, t.FullName);
    }                
}

首先利用 Assembly 類別的 GetExportedTypes 方法來取得組件中所有對外公開的型別,並且用一個迴圈來逐一判斷每個型別是否有實作 ISayHello 介面;是的話就呼叫 Unity 容器的 RegisterType 方法來註冊此對應關係。

透過這種手動檢查型別與註冊型別的方式,我們甚至可以再稍加擴充,掃描特定資料夾中的全部組件,把相容於特定介面的類別全找出來,畢其功於一役。這種掃描組件所有型別並自動註冊型別的作法還有另一個稱呼,叫做自動牽線(auto wiring)。

值得一提的是,在先前的範例中,我們都是用泛型版本的 RegisterType 擴充方法,而在這個場合中是用弱型別的 RegisterType 方法(前篇有解釋何謂弱型別)。是的,當我們無法在編譯時期決定要註冊的型別,或者需要在執行時期手動判斷型別的時候,弱型別的 RegisterType 方法就能派上用場了。

小結

雖然 Unity 還有許多進階功能和設定,但目前所介紹的基本用法,其實就足以讓你在應用程式中利用 Unity 來完成所有的型別對應和解析元件的工作了。儘管稍嫌陽春,但的確是個好的開始。

下一回要看的是如何利用組態檔來設定型別對應。我想,這個入門系列就暫定以組態檔作為 ending 吧!

參考資料:Dependency Injection in .NET by Mark Seemann

沒有留言:

技術提供:Blogger.
回頂端⬆️