前言
第一次接觸Autofac是因為CMS系統--Orchard,后來在一個開源爬蟲系統--NCrawler中也碰到過,隨着深入了解,我越發覺得Ioc容器是Web開發中必不可少的利器。那么,Ioc容器是用來做什么的?用了有什么好處?我相信如果不明白這兩點就很難敞開心扉接受Ioc容器。
傳統解耦設計的弊端
為方便描述,舉個日志的栗子。我簡化實現,一個Log類,一個SaveLog方法。如果其他類想擁有記日志功能,那么只需在內部包含一個Log類型的變量:
{
public void SaveLog( string message) {
// save log here.
}
}
public class ProductService {
private Log _log;
public ProductService() {
_log = new Log ();
}
public void SaveProduct() {
// save product here.
//...
_log.SaveLog( "save 1 product" );
}
}
有經驗的程序員可能會告訴你,這樣做會導致其他類與Log耦合。於是,為了解耦,我們將Log類的功能抽象出來,ILog接口就產生了。如此一來,當其他類需要日志功能時,內含變量就從Log變成了ILog:
void SaveLog( string message);
}
public class Log : ILog
{
public void SaveLog( string message)
{
// save log here.
}
}
public class ProductService
{
private ILog _log;
public ProductService()
{
_log = new Log ();
}
// .......
}
由於ILog被抽象出來,它的實現類可多樣化,保存為txt、xml、數據庫,甚至可擴展出郵件通知功能等。基本上,我看到的國內項目里,所謂的解耦就只能走到這一步了,但這種設計真的是所謂的“靈活,易擴展,高內聚,低耦合”嗎?
現在,我們來模擬需求變更。假設已有TxtLog類把日志保存成txt文件,但使用一段時間后發現:這種日志難以查詢。項目經理決定將日志保存到數據庫,DbLog類應運而生。但是由於整個系統充斥着new TxtLog(),轉換過程實質上就是逐個查找TxtLog替換成DbLog的過程。此時,項目大小決定出錯率,出錯導致日志記錄不全,記錄不全導致系統故障后查不到日志,查不到日志導致找不到原因,找不到原因導致加班,后果太嚴重了。
忽然有天,高潮降臨,經理或老板決定換回txt或換另外一種日志形式,原因不明,或節省成本,或體驗不好,或佞臣讒言,或成心玩你,或與數據庫有世仇,總之--TMD就是要換。於是,悲劇的查找替換再次上演,幾番折騰,千瘡百孔。
而此時此刻,你還會稱贊這種設計“靈活,易擴展”嗎?
邁進IoC大門--改變實例化的方式
現在我們使用Ioc容器--Autofac改進上面的代碼,目標是消除代碼中的new語句,把實例化類的控制權轉移到別的地方,這個地方通常會在一個程序加載時只執行一次的全局方法中。
public static IContainer container;
public void Application_Start() {
ContainerBuilder builder= new ContainerBuilder ();
builder.RegisterType< Log >().As< ILog >();
builder.RegisterType< ProductService >();
container = builder.Build();
var productService = container.Resolve< ProductService >();
}
}
public class ProductService
{
private ILog _log;
public ProductService( ILog log)
{
_log = log;
}
// .......
}
上面代碼中,ContainerBuilder和IContainer是Autofac中的核心類(之后的文章中會介紹,本文不贅述)。當我們要實例化一個ProductService時,需要寫如下代碼:
沒有任何跟Log有關的操作,但productService中的_log變量確已被賦值了一個Log的實例。Ioc容器會在已注冊的組件(類或接口)中匹配實例化參數的類型,一旦發現該類型注冊過,則自動將對應的實例賦值給該類型,這個過程叫做--構造函數注入。
回頭看看那個曾經折磨過我們的TxtLog換DbLog的問題,托Ioc的福,只要在那個全局方法中改一下類型就解決了。
Ioc不僅僅是控制翻轉
也許你會說這個栗子有些極端,實際開發中查找替換的地方並不多,而Ioc只是給實例化換了個地方而已,為了這么一點收益卻要付出巨大的學習成本,是否值得?
實際上,Ioc除了控制反轉外,還提供了很多對實例生命周期的控制,本文使用的Autofac針對流行的框架(如MVC,WCF)提供了簡易整合模塊,以及動態代理功能。在不修改原代碼的前提下,如何為類中方法添加邏輯?Orchard框架通過Autofac和DynamicProxy庫設計出一種很有意思的架構讓兩個不相干的類的方法邏輯能合並在一起。更多細節,我會在之后的系列文章中向大家展示。
結語
5月,天降隕星,三斤大菠蘿來襲,我與基友拔劍而起向邪惡宣戰。如今戰罷收心,准備踏實寫一些文章與大家分享,希望這次能堅持久一些^_^。