IOC概念幫助我們節省了開發與維護的時間,是非常有感的。以往我們如果自己寫一個幫助自己專案的IOC工具,因為時間上的安排通常會做得比較直接簡約,能夠達成抽換(外部嵌入注入或專案內部物件注入)就算是達成目的,已經不錯了。作法大致上是使用一個static的集合來記錄Type與對應的設定要怎麼產生實例。
而許多套件方面卻又提供了更多的情境來使用。不但產生的實例可以注入到各種架構,還可搭配情境、生命週期、記憶體方面限制等等,使用上讓開發者感覺有非常豐富的支援。其中挑選了 Autofac來使用與探討,原因是:
1. 有足夠不藏私的說明文件.
2. 支援各種架構的使用,套件有持續維護 (包含新的語法來簡化程式).
3. 有多個貢獻者提供擴充與問題修正.
4. 為開源套件,代表使用上也可以自己客製擴充.
當然其它套件也有許多優點,用法也大致上都類似。用法幾乎可以用二個方向來描述 : 第一個是先註冊(Register)。第二個是實現實例(Resolve).
舉個簡單的例子:
//註冊
builder.RegisterType<TextWriterLog>().AsSelf().As<ILog>().SingleInstance();
//產生實例
ILog provider = null;
using (var scope = AutofacContainer.builder.BeginLifetimeScope())
{
if (scope.TryResolve<ILog>(out provider))
{
provider.WriteByLog("\n" + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") , "ILog write message!");
}
}
如此即能實現簡單的IOC。
而 Autofac 還能夠讓產生實例的註冊再包含一些設定,如委派的函示、條件的安排,甚至其它情境如 Decorator pattern 與 Adapter pattern 的使用。這時會想,它是如何運作的? 它是怎麼達成的?
Autofac 會用一個容器將所有資訊存入,並建各種引擎來做對應的事。最終 build 到 容器中。
先觀察註冊時的圖敘述:
在註冊服務這個動作中,最終要產生 RegistrationBuilder 這個物件, 包含了要如何變成實體的資料 ActivatorData 、變成什麼樣的服務 RegistrationData 與 其它協助過濾輔助的資料 RegistrationStyle。
其中的 RegistrationData.DeferredCallback,要存到 ContainerBuilder.configurationCallbacks List集合中,先Hook 執行內容 RegistrationBuilder.RegisterSingleComponent於此,也就是告訴容器這個服務未來要用此方式來產生製造實例的資訊。
接著看ContainerBuiler註冊時,要產生容器與將資訊放入容器中:
ContainerBuilder為建構容器的阿大引擎。之前透過此大引擎註冊好服務後,這邊開始做建構容器的動作。容器Container內有一個小引擎物件,ComponentRegistry, 它的作用是將ConponentRegistration存到自己的_registrations 與 _serviceinfo 集合內。透過Register方法先將預設的(新產生的必要服務) 轉至 ConponentRegistration 然後存到 自己的_registrations 與 _serviceinfo 集合。
接下來要將註冊好的資訊 (RegistrationBuilder) 將此內容由註冊時所Hook的驅動執行,產生ComponentRegistration存入ComponentRegistry的_registrations 與 _serviceinfo 集合內。
所以我們拉出視野,綜觀ContainerBuilder.build()在做的事:
1. 先建立一個新的 Container。
2. 產生新的ConponentRegistration,裡面放預設必要的基礎服務,放到 Container 的 ComponentRegistry 中的_registrations 與 _serviceinfo 集合。
3. 將開發者註冊好的 RegistrationBuilder 轉成 ConponentRegistration 並逐一放到 Container 的 ComponentRegistry 中的_registrations 與 _serviceinfo 集合。
做好以上準備後,接下來就是於程式中去實現實例,以下圖稍作說明:
這邊看到有做遞迴,當一個實例的建構子參數還有需要用到所註冊的服務來產生實例,會接著再去跑一次抓服務資訊來對應並產生實例。所以其實它是可以巢狀地,一層一層的去注入已註冊好的服務。
如 new OutterClass( new MediamClass( new InnerClass()) , 此三個Class 皆可利用註冊服務的方式去resolve出來,而且可以神奇的只下一個指令:
container.Resolve<OutterClass>();
就可以幫忙產生好實例了,實在非常簡便,令很多國內外開發者讚嘆。
看到這邊,我相信要真的體會實際運作是有些技巧與想像。也就是仍有一點抽象。所以想了一個例子來做白話描述,讓大家可以想像得出來大致上的運作、作法,或者其實是類似做了什麼:
某人帶了一個籃子(container) , 買了放了各種蛋,並收集了要做哪些與蛋有關的菜單(service) ,貼上標籤,內容為蛋種類、配料與食譜,要準備做出哪個菜單,
標籤註明主要以快炒方式煮這些蛋(Hook RegistrationBuilder.RegisterSingleComponent ),
到了某餐廳(應用程式主體或某 controller action),
要找人做菜 (準備 resolve),
然後到找到某個有證照廚師,把這些資訊給他,請他做菜 (委派),
菜單要炒雞蛋, 依照標籤找到白雞蛋與火腿玉米配料,最後 快炒方式煮出火腿玉米炒蛋 (用火腿玉米裝飾了炒蛋,裝飾者模式)。
找到另一名廚師,這名廚師要先做到洗乾淨手與戴口罩(lambda expression and before prepare event)才行,然後請他做菜(委派),菜單要炒鹹蛋, 依照標籤找到鹹鴨蛋與苦瓜等配料,最後 快炒方式煮出苦瓜炒鹹蛋 (用苦瓜裝飾了炒蛋,裝飾者模式)。
之後要怎麼用這些煮好的食物看用途!
下一篇是介紹如何在此套件擴充客製的功能。
When you subscribe to the blog, we will send you an e-mail when there are new updates on the site so you wouldn't miss them.
評論