這篇距離上一篇 Unity 筆記,竟然已經兩年了...Orz
本文內容摘自《.NET 相依性注入》,主角是 Unity 的自動註冊(auto-registraction)功能。
共用的範例程式
為了避免往後重複太多相同的程式碼,這裡先列出共用的介面與類別。
假設情境
假設應用程式需要提供訊息通知機制,而此機制需支援多種發送管道,例如:電子郵件、
簡訊服務(Short Message Service)、行動應用程式的訊息推送(push notification)等等。簡單
起見,這裡僅實作其中兩種服務,而且發送訊息的部分都使用簡單的 Console.WriteLine()
來輸出訊息,方便觀察程式的執行結果。
設計
用一個 NotificationManager 類別來作為整個訊息通知功能的管理員。各類訊息通知機制則由以下類別提供:
以上三個類別均實作同一個介面: IMessageService, 而且 NotificationManager 只知道
IMessageService 介面,而不直接依賴具象類別。以下類別圖描繪了它們的關係:
程式碼
訊息通知管理員的相關程式碼:
這裡採用了 Constructor Injection 的注入方式,由建構函式傳入訊息服務。其中的 Notify 方法則利用事先注入的訊息服務來發送訊息給指定的接收對象(引數 ‘to‘)。在真實世界中,你可能會需要用額外的類別來代表訊息接收對象(例如設計一個 MessageRecipient 類 別來封裝收件人的各項資訊),這裡為了示範而對這部分做了相當程度的簡化。
底下是各類訊息服務的程式碼:
自動註冊
「自動註冊」的另一種說法是「依慣例註冊」(registration by convention),主要用意是讓開發人員能夠少寫一些註冊型別的程式碼。Unity 是透過擴充方法 RegisterTypes(注意 “Types” 是複數)來提供自動註冊的功能。此方法有兩個多載版本,底下是它們的原型宣告:
先來看一個簡單的用法,使用的是上列標示(1) 的RegisterTypes 多載方法:
這裡省略了大部分非必要的參數,而只傳入兩個參數:
假設此範例應用程式目前已載入的組件當中已經有一個EmailService 類別,而且該類別實作了IMessageService 介面,那麼當應用程式需要取得符合IMessageService 介面的物件時,由於Unity 已經自動尋找並註冊了相關型別,於是我們便可透過先前提過的型別解析方法來取得物件:
解決重複型別對應的問題
預設情況下,「自動註冊」會採用未具名的「預設註冊」(這名詞前面有提過),而且在碰到欲解析的類別有多載建構函式時,會根據一套既定規則來挑選建構函式。因此,如果只用剛才的簡單範例來進行自動註冊,很可能會在程式執行時發生型別註冊失敗或無法解析 特定型別的錯誤。
就拿前面提過的訊息通知服務的範例來說, 由於 EmailService 和 SmsService 都實作了 IMessageService 介面,若以上述範例程式來進行自動註冊,那麼當程式執行時,將會在呼叫 RegisterTypes 方法的地方發生重複型別對應的錯誤:
碰到這種情形,你可以透過 RegisterTypes 方法的 getName 參數(它是個委派)來為不同的類別提供不同的註冊名稱。
其中的WithName 是Unity 提供的輔助類別,而它的TypeName 方法可傳回指定型別的名稱。也就是說,如果沒有特別複雜的需求,我們可以直接把WithName.TypeName 方法傳遞給getName 參數。如此一來,每一個具象類別的型別對應關係就會以該類別的名稱來命名。於是,在解析物件的時候,也必須明白指定註冊名稱。像這樣:
另一個避免型別對應重複的解決方法,是利用參數overwriteExistingMappings。當你只需要找到任何一個可用的實作類別,而不在乎 Unity 最終會採用哪一個,此時便可將參數 overwriteExistingMappings 指定為 true,如此亦可避免型別對應重複的問題,而且無須使用具名註冊。參考以下範例:
接著要來進一步了解 AllClasses 和WithMappings 類別還提供了哪些輔助功能。(略,請參考本書內容,或查看線上文件:AllClasses,WithMappings)
無恥連結:[書訊]《.NET 相依性注入》
本文內容摘自《.NET 相依性注入》,主角是 Unity 的自動註冊(auto-registraction)功能。
共用的範例程式
為了避免往後重複太多相同的程式碼,這裡先列出共用的介面與類別。
假設情境
假設應用程式需要提供訊息通知機制,而此機制需支援多種發送管道,例如:電子郵件、
簡訊服務(Short Message Service)、行動應用程式的訊息推送(push notification)等等。簡單
起見,這裡僅實作其中兩種服務,而且發送訊息的部分都使用簡單的 Console.WriteLine()
來輸出訊息,方便觀察程式的執行結果。
設計
用一個 NotificationManager 類別來作為整個訊息通知功能的管理員。各類訊息通知機制則由以下類別提供:
- EmailService:透過電子郵件發送訊息
- SmsService:透過簡訊服務發送訊息
以上三個類別均實作同一個介面: IMessageService, 而且 NotificationManager 只知道
IMessageService 介面,而不直接依賴具象類別。以下類別圖描繪了它們的關係:
程式碼
訊息通知管理員的相關程式碼:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface INotificationManager | |
{ | |
void Notify(string to, string msg); | |
} | |
public class NotificationManager : INotificationManager | |
{ | |
private readonly IMessageService _msgService = null; | |
// 從建構函式注入訊息服務物件。 | |
public NotificationManager(IMessageService svc) | |
{ | |
_msgService = svc; | |
} | |
// 利用訊息服務來發送訊息給指定對象。 | |
public void Notify(string to, string msg) | |
{ | |
_msgService.SendMessage(to, msg); | |
} | |
} |
這裡採用了 Constructor Injection 的注入方式,由建構函式傳入訊息服務。其中的 Notify 方法則利用事先注入的訊息服務來發送訊息給指定的接收對象(引數 ‘to‘)。在真實世界中,你可能會需要用額外的類別來代表訊息接收對象(例如設計一個 MessageRecipient 類 別來封裝收件人的各項資訊),這裡為了示範而對這部分做了相當程度的簡化。
底下是各類訊息服務的程式碼:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IMessageService | |
{ | |
void SendMessage(string to, string msg); | |
} | |
public class EmailService : IMessageService | |
{ | |
public void SendMessage(string to, string msg) | |
{ | |
Console.WriteLine(" 透過 EmailService 發送郵件給 {0}。", to); | |
} | |
} | |
public class SmsService : IMessageService | |
{ | |
public void SendMessage(string to, string msg) | |
{ | |
Console.WriteLine(" 透過 SmsService 發送簡訊給 {0}。", to); | |
} | |
} |
自動註冊
「自動註冊」的另一種說法是「依慣例註冊」(registration by convention),主要用意是讓開發人員能夠少寫一些註冊型別的程式碼。Unity 是透過擴充方法 RegisterTypes(注意 “Types” 是複數)來提供自動註冊的功能。此方法有兩個多載版本,底下是它們的原型宣告:
先來看一個簡單的用法,使用的是上列標示(1) 的RegisterTypes 多載方法:
// 範例:讓Unity 容器自動掃描組件並註冊型別。 var container = new UnityContainer(); container.RegisterTypes( AllClasses.FromLoadedAssemblies(), // 掃描目前已經載入此應用程式的全部組件。 WithMappings.FromAllInterfaces); // 尋找所有介面。
這裡省略了大部分非必要的參數,而只傳入兩個參數:
- 參數 types 是具象類別清單。這裡使用了 Unity 提供的輔助類別 AllClasses 的 FromLoadAssemblies 方法來提供類別清單。
- 參數 getFromTypes 是個用來取得來源型別清單的委派(delegate)。「來源型別」 指的就是註冊型別對應關係時的抽象型別。這裡傳入的委派是指向Unity 的 WithMappings.FromAllInterfaces 方法,作用是取得所有類別所實作的介面。
假設此範例應用程式目前已載入的組件當中已經有一個EmailService 類別,而且該類別實作了IMessageService 介面,那麼當應用程式需要取得符合IMessageService 介面的物件時,由於Unity 已經自動尋找並註冊了相關型別,於是我們便可透過先前提過的型別解析方法來取得物件:
var svc = container.Resolve();
解決重複型別對應的問題
預設情況下,「自動註冊」會採用未具名的「預設註冊」(這名詞前面有提過),而且在碰到欲解析的類別有多載建構函式時,會根據一套既定規則來挑選建構函式。因此,如果只用剛才的簡單範例來進行自動註冊,很可能會在程式執行時發生型別註冊失敗或無法解析 特定型別的錯誤。
就拿前面提過的訊息通知服務的範例來說, 由於 EmailService 和 SmsService 都實作了 IMessageService 介面,若以上述範例程式來進行自動註冊,那麼當程式執行時,將會在呼叫 RegisterTypes 方法的地方發生重複型別對應的錯誤:
An unhandled exception of type ‘Microsoft.Practices.Unity.DuplicateTypeMappingException’ occurred in Microsoft.Practices.Unity.RegistrationByConvention.dll
碰到這種情形,你可以透過 RegisterTypes 方法的 getName 參數(它是個委派)來為不同的類別提供不同的註冊名稱。
var container = new UnityContainer(); container.RegisterTypes( AllClasses.FromLoadedAssemblies(), // 掃描目前已經載入此應用程式的全部組件。 WithMappings.FromAllInterfaces, // 尋找所有介面。 getName: WithName.TypeName); // 使用類別名稱來當作註冊名稱。
其中的WithName 是Unity 提供的輔助類別,而它的TypeName 方法可傳回指定型別的名稱。也就是說,如果沒有特別複雜的需求,我們可以直接把WithName.TypeName 方法傳遞給getName 參數。如此一來,每一個具象類別的型別對應關係就會以該類別的名稱來命名。於是,在解析物件的時候,也必須明白指定註冊名稱。像這樣:
var svc = container.Resolve("EmailService");
另一個避免型別對應重複的解決方法,是利用參數overwriteExistingMappings。當你只需要找到任何一個可用的實作類別,而不在乎 Unity 最終會採用哪一個,此時便可將參數 overwriteExistingMappings 指定為 true,如此亦可避免型別對應重複的問題,而且無須使用具名註冊。參考以下範例:
var container = new UnityContainer(); container.RegisterTypes( AllClasses.FromLoadedAssemblies(), // 掃描目前已經載入此應用程式的全部組件。 WithMappings.FromAllInterfaces, // 尋找所有介面。 overwriteExistingMappings: true); // 有多個符合條件的類別時,採用最後找到的那個。
接著要來進一步了解 AllClasses 和WithMappings 類別還提供了哪些輔助功能。(略,請參考本書內容,或查看線上文件:AllClasses,WithMappings)
無恥連結:[書訊]《.NET 相依性注入》
沒有留言: