1. 程式人生 > >Ioc容器Autofac系列(1)-- 初窺

Ioc容器Autofac系列(1)-- 初窺

轉自:兩會的部落格

前言

第一次接觸Autofac是因為CMS系統--Orchard,後來在一個開源爬蟲系統--NCrawler中也碰到過,隨著深入瞭解,我越發覺得Ioc容器是Web開發中必不可少的利器。那麼,Ioc容器是用來做什麼的?用了有什麼好處?我相信如果不明白這兩點就很難敞開心扉接受Ioc容器。

傳統解耦設計的弊端

為方便描述,舉個日誌的栗子。我簡化實現,一個Log類,一個SaveLog方法。如果其他類想擁有記日誌功能,那麼只需在內部包含一個Log型別的變數:

  public class Log
  {
public void SaveLog(string message) {

          // save log here.
      }
  }

  public class ProductService {
      private Log _log;
      public ProductService() {
          _log = newLog();
      }

      public void SaveProduct() {
          // save product here.
          //...
          _log.SaveLog("save 1 product");
      }
  }

有經驗的程式設計師可能會告訴你,這樣做會導致其他類與Log

耦合。於是,為了解耦,我們將Log類的功能抽象出來,ILog介面就產生了。如此一來,當其他類需要日誌功能時,內含變數就從Log變成了ILog

public interface 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 = newLog();
        }
        // .......
    }

由於ILog被抽象出來,它的實現類可多樣化,儲存為txt、xml、資料庫,甚至可擴展出郵件通知功能等。基本上,我看到的國內專案裡,所謂的解耦就只能走到這一步了,但這種設計真的是所謂的“靈活,易擴充套件,高內聚,低耦合”嗎?

現在,我們來模擬需求變更。假設已有TxtLog類把日誌儲存成txt檔案,但使用一段時間後發現:這種日誌難以查詢。專案經理決定將日誌儲存到資料庫,DbLog類應運而生。但是由於整個系統充斥著new TxtLog(),轉換過程實質上就是逐個查詢TxtLog替換成DbLog的過程。此時,專案大小決定出錯率,出錯導致日誌記錄不全,記錄不全導致系統故障後查不到日誌,查不到日誌導致找不到原因,找不到原因導致加班,後果太嚴重了。

忽然有天,高潮降臨,經理或老闆決定換回txt或換另外一種日誌形式,原因不明,或節省成本,或體驗不好,或佞臣讒言,或成心玩你,或與資料庫有世仇,總之--TMD就是要換。於是,悲劇的查詢替換再次上演,幾番折騰,千瘡百孔。

而此時此刻,你還會稱讚這種設計“靈活,易擴充套件”嗎?

邁進IoC大門--改變例項化的方式

現在我們使用Ioc容器--Autofac改進上面的程式碼,目標是消除程式碼中的new語句,把例項化類的控制權轉移到別的地方,這個地方通常會在一個程式載入時只執行一次的全域性方法中。

public class Global {
    public static IContainer container;
    public void Application_Start() {
        ContainerBuilder builder=newContainerBuilder();
        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;
    }
    // .......
}

上面程式碼中,ContainerBuilderIContainer是Autofac中的核心類(之後的文章中會介紹,本文不贅述)。當我們要例項化一個ProductService時,需要寫如下程式碼:

var productService = container.Resolve<ProductService>();

沒有任何跟Log有關的操作,但productService中的_log變數確已被賦值了一個Log的例項。Ioc容器會在已註冊的元件(類或介面)中匹配例項化引數的型別,一旦發現該型別註冊過,則自動將對應的例項賦值給該型別,這個過程叫做--建構函式注入。

回頭看看那個曾經摺磨過我們的TxtLogDbLog的問題,託Ioc的福,只要在那個全域性方法中改一下型別就解決了。

Ioc不僅僅是控制翻轉

也許你會說這個栗子有些極端,實際開發中查詢替換的地方並不多,而Ioc只是給例項化換了個地方而已,為了這麼一點收益卻要付出巨大的學習成本,是否值得?

實際上,Ioc除了控制反轉外,還提供了很多對例項生命週期的控制,本文使用的Autofac針對流行的框架(如MVC,WCF)提供了簡易整合模組,以及動態代理功能。在不修改原始碼的前提下,如何為類中方法新增邏輯?Orchard框架通過Autofac和DynamicProxy庫設計出一種很有意思的架構讓兩個不相干的類的方法邏輯能合併在一起。更多細節,我會在之後的系列文章中向大家展示。