[.net 物件導向程式設計深入](31)實戰設計模式——使用Ioc模式(控制反轉或依賴注入)實現鬆散耦合設計(1)

yubinfeng發表於2017-03-17

[.net 物件導向程式設計深入](31)實戰設計模式——使用IoC模式(控制反轉或依賴注入)實現鬆散耦合設計(1)

1,關於IOC模式 

先看一些名詞含義:

IOC: Inversion of control 控制反轉,簡稱

DI: Dependency Injection 依賴注入,簡稱

DIP: 依賴倒置原則 一種軟體架構設計的原則(抽象概念),“設計模式使用場景及原則”一篇中介紹過設計模式的幾種原則之一。

IoC容器:依賴注入的框架,用來對映依賴,管理物件建立和生存週期(DI框架)。

(1)IOC和DI,是站在不同角度的描述,本身是同一個概念

先說說這兩個對於初次接觸到的同學難以理解的概念,首先這兩個東東是一回事,只是因為角度不同,有了兩個名字。

舉個不太恰當的例子,比如一個人,爸爸叫你兒子,爺爺叫你孫子,那這個兒子和孫子都是你,是同一個人,只是站的角度不同,這麼說容易理解了吧。

依賴注入(DI)是從應用程式的角度在描述,可以把依賴注入描述完整點:應用程式依賴容器建立並注入它所需要的外部資源;

而控制反轉(IOC)是從容器的角度在描述,描述完整點:容器控制應用程式,由容器反向的嚮應用程式注入應用程式所需要的外部資源。

(2)是一種大粒度的設計模式

和GOF的23種設計模式相比較。IOC模式(通常中文稱"依賴注入"或“依賴倒置”),由於出現時間比較晚,沒有被收錄。

其次和23種設計模式相比較,它的粒度更大一些,和MVC模式一樣,通常到架構層面了,而不是具體程式碼片段級別。理解到這裡就可以了。

但它仍然是設計模式,只是和23種設計模式比,23種模式是戰術層面,IOC模式是戰略層面的。 

依賴注入對映到物件導向程式開發中就是:高層類應該依賴底層基礎設施來提供必要的服務。

編寫鬆耦合的程式碼說起來很簡單,但是實際上寫著寫著就變成了緊耦合。

一個比較難理解的正式定義如下:

依賴注入(Dependency Injection),是這樣一個過程:由於某客戶類只依賴於服務類的一個介面,而不依賴於具體服務類,所以客戶類只定義一個注入點。

在程式執行過程中,客戶類不直接例項化具體服務類例項,而是客戶類的執行上下文環境或專門元件負責例項化服務類,然後將其注入到客戶類中,保證客戶類的正常執行。

下面的說明比較容易理解: 

     理解DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰注入誰,注入了什麼”,那我們來深入分析一下: 

  ●誰依賴於誰:當然是應用程式依賴於IoC容器; 

  ●為什麼需要依賴:應用程式需要IoC容器來提供物件需要的外部資源; 

  ●誰注入誰:很明顯是IoC容器注入應用程式某個物件,應用程式依賴的物件; 

  ●注入了什麼:就是注入某個物件所需要的外部資源(包括物件、資源、常量資料)。 

  IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制物件這一個層面,很難讓人想到誰來維護物件關係),

     所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,“依賴注入”明確描述了“被注入物件依賴IoC容器配置依賴物件”。 

2,依賴注入的作用

依賴注入不是目的,它是一系列工具和手段。

最終的目的是幫助我們開發出鬆散耦合(loose coupled)、可維護、可測試的程式碼和程式。

這條原則的做法是大家熟知的面向介面,或者說是面向抽象程式設計。 

常見的三層架構中,雖然表面上表現、業務、資料分層設計。但在資料庫層往往會產生一些與具體業務有關的類,而且如果不嚴格遵循程式碼規範,會導致產生表現層直接new資料層的情況。

如果要換個資料來源呢?假如不使用ADO.NET等試,改為Http呢?這將是領域邏輯層和表現層與之耦合的程式碼要進行大量更動。 

這樣使得整個系統緊耦合,並且可測試性差。

在系統設計過程中,各個類從上層到下層類之間必然會產生耦合,如果完全沒有耦合,那麼這個類或程式集就可以從專案中移除了。

因此如何使之達到鬆散耦合,從而提高可測試性呢?依賴注入將能很好的解決上述問題。 

3,IOC模式應用示例

下面以一個簡單購物過程為例來說明IOC模式如何實現鬆散耦合。

(1)傳統三層模式

程式碼如下:

    public class DalSqlServer
    {
        public void Add()
        {
            Console.WriteLine("在資料庫中新增一條訂單!");
        }
    }
    public class Order
    {
        private readonly DalSqlServer dal = new  DalSqlServer();//新增一個私有變數儲存資料庫操作的物件
        public void Add()
        {
            dal.Add();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Order order = new Order();
            order.Add();
            Console.Read();

        }
    }

執行結果:

 上面的程式碼看著功能都實現了,然而突然老闆說,SqlServer要花錢買,我們使用免費的Access吧,好吧,那就改改嘍。改動後如下:

這時,我們只能再增加一個DalAcess類,來解決,程式碼如下:

public class DalAccess
    {
        public void Add()
        {
            Console.WriteLine("在Access資料庫中新增一條訂單!");
        }
    }
   public class Order
    {
        private readonly DalAccess dal = new DalAccess();//新增一個私有變數儲存Access資料庫操作的物件
        public void Add()
        {
            dal.Add();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Order order = new Order();
            order.Add();
            Console.Read();

        }
    }

執行結果:

正在這時,老闆來了,說最近生意好,訂單劇增,改成MySql資料庫吧,矇蔽了吧,示例程式碼中只有一個Add()的方法,可實際專案中,不知道有多少工作量了。

(2)使用IOC模式改進

只所以在需求變化時,我們的程式碼改動量如此之大,是因為耦合,耦合,耦合。

前面說了耦合不可能不存在,如果不存在,那這個程式碼就可以從專案中移除了,但是要讓讓程式碼可維護性強,就必須使用模式化的開發

當然依賴注入就能解決上述問題,依賴注入(DI),它提供一種機制,將需要依賴(低層模組)物件的引用傳遞給被依賴(高層模組)物件

我們示例中低層模組就是DalSqlServer,DalAccsess,DalMySql等,高層模組就是Order.

那麼如何在Order內部不依賴DalSqlServer,DalAccsess,DalMySql的情況下傳遞呢,

這就需要對DalSqlServer,DalAccsess,DalMySql進行抽象設計,我們設計一個IData資料介面物件,讓DalSqlServer,DalAccsess,DalMySql去具體實現它,

在傳遞過程中,我們只需要訂單處理Order和資料介面層IData耦合,這樣,即使資料庫再變化,但IData是穩定的。也不需要再改動Order中的程式碼了。是不是很不錯的解耦呢?

改進後如下:

程式碼如下:

   public class DalOracle : IData
    {
        public void Add()
        {
            Console.WriteLine("在Oracle資料庫中新增一條訂單!");
        }
    }
    public class DalAccess : IData
    {
        public void Add()
        {
            Console.WriteLine("在Access資料庫中新增一條訂單!");
        }
    }
    public class DalMySql : IData
    {
        public void Add()
        {
            Console.WriteLine("在MySql資料庫中新增一條訂單!");
        }
    }
    public class DalSqlServer : IData
    {
        public void Add()
        {
            Console.WriteLine("在SqlServer資料庫中新增一條訂單!");
        }
    }

定單處理類:

  public class Order
    {
        private IData idata; //定義私有變數儲存抽象出來的資料介面

        /// <summary>
        /// 通過建構函式注入
        /// </summary>
        /// <param name="iData"></param>
        public Order(IData iData)
        {
            this.idata = iData; //傳遞依賴
        }

        public void Add()
        {
            idata.Add();
        }
    }

展示:

    class Program
    {
        static void Main(string[] args)
        {
             //定義空訂單
             Order order=null;

            //使用SqlServer
            order = new Order(new DalSqlServer());
            order.Add();

            //使用Oracle
            order = new Order(new DalOracle());
            order.Add();

            //使用Accesss
            order = new Order(new DalAccess());
            order.Add();

            //使用MySql
            order = new Order(new DalMySql());
            order.Add();

            Console.Read();
        }
    }

 這樣就可以隨意切換資料庫了,執行結果如下:

(3)更進一步改進,控制反轉

上面說到站在另一個角度講,我們把選擇資料庫的許可權交給第三方,是不是可以不用每次在建立訂單時都指定依賴物件(即具體資料類),也就是控制反轉。

針對上面的每次指定依賴物件的問題,處理的方式很多,最簡單的我們可以通過一個配置檔案來指定所使用的具體資料庫,在傳遞時通過反射的方式來對映資料類。

這樣就就靈活多了。

4,IOC注入的幾種方式

 (1)建構函式注入

上面示例就是這種方式

(2)屬性注入

    public class Order
    {
        public IData Idata{get;set;}       

        public void Add()
        {
            this.Idata.Add();
        }
    }
class Program
    {
        static void Main(string[] args)
        {
             //定義空訂單
             Order order=null;

            //使用SqlServer
            order = new Order();
            order.Idata = new DalSqlServer();
            order.Add();

            //使用Oracle
            order = new Order();
            order.Idata = new DalOracle();
            order.Add();

            //使用Accesss
            order = new Order();
            order.Idata = new DalAccess();
            order.Add();

            //使用MySql
            order = new Order();
            order.Idata = new DalMySql();
            order.Add();

            Console.Read();
        }
    }

(3)方法注入

 public class Order
    {
        private IData idata; //私有變數儲存抽象介面

        //通過Idata方法傳遞依賴
        public void Idata(IData idata)
        {
            this.idata = idata; 
        }
        public void Add()
        {
            this.idata.Add();
        }
    }
   class Program
    {
        static void Main(string[] args)
        {
             //定義空訂單
             Order order=null;

            //使用SqlServer
            order = new Order();
            order.Idata( new DalSqlServer());
            order.Add();

            //使用Oracle
            order = new Order();
            order.Idata(new DalOracle());
            order.Add();

            //使用Accesss
            order = new Order();
            order.Idata(new DalAccess());            
            order.Add();

            //使用MySql
            order = new Order();
            order.Idata(new DalMySql());
            order.Add();

            Console.Read();
        }
    }

5,IOC容器(或DI框架) 

對於大型專案來說,相互依賴的元件比較多。如果還用手動的方式,自己來建立和注入依賴的話,顯然效率很低,而且往往還會出現不可控的場面。正因如此,IoC容器誕生了。IoC容器實際上是一個DI框架,它能簡化我們的工作量。它包含以下幾個功能: 

動態建立、注入依賴物件。
管理物件生命週期。
對映依賴關係。 

比如比較知名的“基於DDD的現代ASP.NET開發框架--ABP”使用Castle Windsor框架處理依賴注入。它是最成熟的DI框架之一。還有很多其他的框架,如Unity,Ninject,StructureMap,Autofac等等。

下面是園友整理出來的一些常用的IOC容器及官網:

(1). Ninject: http://www.ninject.org/

(2). Castle Windsor: http://www.castleproject.org/container/index.html

(3). Autofac: http://code.google.com/p/autofac/

(4). StructureMap: http://docs.structuremap.net/

(5). Unity: http://unity.codeplex.com/

(6). MEF: http://msdn.microsoft.com/zh-cn/library/dd460648.aspx

(7). Spring.NET: http://www.springframework.net/

(8). LightInject: http://www.lightinject.net/ (推薦使用Chrome瀏覽器訪問)

6,總結

(1)依賴注入,是一種結構型的設計模式,即IOC模式。

(2)IOC意思為控制反轉和依賴注入是同一概念的不同角度的說法。

(3)依賴注入是讓我們的應用程式依賴於抽象出來的服務類的介面,而不是具體的服務類,從而在具體的服務類發生需求變化時,我們注入新的服務介面,做到鬆散耦合。

(4)依賴注入有三種簡單的方式,即建構函式注入,屬性注入,方法注入。

(5)在大型專案中為了解決手動建立注入的效率低下,誕生了IOC容器,常見的有:Unity、Ninject、StructureMap、Autofac、Spring.NET等。

7,原始碼

https://github.com/yubinfeng/BlogExamples.git

==============================================================================================

返回目錄

<如果對你有幫助,記得點一下推薦哦,如有有不明白或錯誤之處,請多交流>

<對本系列文章閱讀有困難的朋友,請先看 《.net 物件導向程式設計基礎》 和 《.net 物件導向程式設計進階》 >

<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>

.NET 技術交流群:467189533 H.NET 技術交流群

==============================================================================================

相關文章