方便閱讀起見,這裡再列出關鍵程式碼:
1
2
| var container = new UnityContainer(); ISayHello hello = container.Resolve<SayHelloInEnglish>(); |
用法很簡單,就只是先建立一個 UnityContainer 物件,然後再呼叫該容器物件的 Resolve 方法。
這裡使用的 Resolve 方法是個泛型方法,它接受一個型別參數,用來指定欲建立之物件的型別。由於傳入的型別是 SayHelloInEnglis,Unity 容器就知道要用這個型別來建立物件實體。換言之,Unity 會去呼叫 SayHelloInEnglish 的建構子。
在此範例中,我們並沒有為 SayHelloInEnglish 撰寫任何建構子,所以 Unity 容器會自動使用 C# 編譯器幫我們產生的預設建構子來建立物件實體。
註:預設建構子就是不帶任何參數的建構子。當你的類別沒有提供任何建構子,C# 編譯器會自動幫你產生一個。如果有在類別中定義任何建構子,則 C# 編譯器不會幫你產生預設建構子。
答案是:預設情況下,Unity 會選擇使用參數最多的那個建構子來建立物件。
進一步思考剛才那句話,也許你會有個疑問:「Unity 在幫我們建立物件時,傳遞給建構子的各個參數也必須要先建立實體,那麼在建立這些參數的物件時,又是使用哪個建構子呢?」
還是同樣規則,亦即使用參數個數最多的那個建構子。
比如說,把先前的範例改一下,為 SayHelloInEnglish 類別提供兩個建構子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| class SayHelloInEnglish : ISayHello { public SayHelloInEnglish() { } public SayHelloInEnglish(User aUser) { Console.WriteLine( "SayHelloInEnglish(User aUser) is called" ); } public void Run() { Console.WriteLine( "Hello, Unity!" ); } } |
按照先前描述的規則,當 Unity 在建立 SayHelloInEnglish 的物件時,會使用帶最多參數的建構子,也就是需要傳入一個 User 物件的那個建構子。假設 User 類別的建構子也有兩個:
1
2
3
4
5
6
7
8
9
10
11
12
| class User { public User() { Console.WriteLine( "無名氏" ); } public User( string name) { Console.WriteLine(name); } } |
那麼當 Unity 在使用 SayHelloInEnglish(User aUser) 這個版本建構子時,又需要先建立 User 物件,而 User 類別的建構子有兩種口味,於是選擇參數最多的那個,也就是User(string name)。問題是,要再建立參數 name 的物件實體時,Unity 並不是選擇 String 的預設建構子,而一樣是用最多參數的建構子:
1
2
3
4
5
6
| String( sbyte * value, int startIndex, int length, Encoding enc ) |
如此一路下去,Unity 最終會碰到無法建立某一層建構子所需的參數物件而拋出例外(exception)。於是程式執行時便會看到如下錯誤訊息:
Unhandled Exception: Microsoft.Practices.Unity.ResolutionFailedException: Resolu
tion of the dependency failed, type = "UnityDemo01.SayHelloInEnglish", name = "(
none)".
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The type String cannot be constructed.
You must configure the container to supply this value.
前面描述的,都是在預設情況下所產生的行為,而此預設行為是可以改的。以剛才的範例來說,如果你希望 Unity 在建立 User 物件時使用預設建構子(不帶任何參數的建構子),就可以為那個建構子套用 InjectionConstructorAttribute。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
| class User { [InjectionConstructor] public User() { Console.WriteLine( "無名氏" ); } public User( string name) { Console.WriteLine(name); } } |
如此一來,Unity 在建立物件時就會去用你特別指定的建構子,程式也就能順利執行,不會再發生先前的錯誤了。執行結果如下圖:
小結
到目前為止,我們已經知道如何透過 Unity 容器的 Resolve 方法來幫我們建立物件,也知道在建立物件時,Unity 預設會使用最多參數的建構子,而我們又如何利用 InjectionConstructorAttribute 來改變此預設行為。
可是,在呼叫 Resolve 方法時,我們傳入的型別是 SayHelloInEnglish,而不是 ISayHello 介面。這種寫法不是挺好,因為這會讓我們的程式跟 SayHelloInEnglish 類別綁得太緊。而且,從 Resolve 方法的名稱應該就看得出來,這個方法並不單單只是幫我們建立類別的 instance,它其實還有「解析型別」的功能。
如果你看過先前的 Dependency Injection 筆記,也許還記得裡面有提到的一個原則:要針對介面,而非針對實作類別來寫程式。所以,在下一篇文章裡,我會繼續修改這個範例程式,朝向「針對介面寫程式」的目標前進,並展示 Resolve 方法的主要功能:解析物件型別。
參考資料:Dependency Injection in .NET by Mark Seemann
from : http://huan-lin.blogspot.com/2012/07/unity-2.html
沒有留言:
張貼留言