[.net 面向對象程序設計深入](31)實戰設計模式——使用Ioc模式(控制反轉或依賴注入)實現松散耦合設計(1)


[.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 技術交流群

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM