深刻理解DIP、IoC、DI以及IoC容器

摘要

面向對象設計(OOD)有助於咱們開發出高性能、易擴展以及易複用的程序。其中,OOD有一個重要的思想那就是依賴倒置原則(DIP),並由此引伸出IoC、DI以及Ioc容器等概念。經過本文咱們將一塊兒學習這些概念,並理清他們之間微妙的關係。html


 

目錄

 


 

前言

對於大部分小菜來講,當聽到大牛們高談DIP、IoC、DI以及IoC容器等名詞時,有沒有瞬間石化的感受?其實,這些「高大上」的名詞,理解起來也並非那麼的難,關鍵在於入門。只要咱們入門了,而後按部就班,假以時日,天然水到渠成。spring

好吧,咱們先初略瞭解一下這些概念。數據庫

依賴倒置原則(DIP):一種軟件架構設計的原則(抽象概念)。設計模式

控制反轉(IoC):一種反轉流、依賴和接口的方式(DIP的具體實現方式)。瀏覽器

依賴注入(DI):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)。架構

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

哦!也許你正爲這些陌生的概念而傷透腦筋。不過不要緊,接下來我將爲你一一道破這其中的玄機。函數

 

依賴倒置原則(DIP)

在講概念以前,咱們先看生活中的一個例子。post

                                                            圖1   ATM與銀行卡性能

相信大部分取過錢的朋友都深有感觸,只要有一張卡,隨便到哪一家銀行的ATM都能取錢。在這個場景中,ATM至關於高層模塊,而銀行卡至關於低層模塊。ATM定義了一個插口(接口),供全部的銀行卡插入使用。也就是說,ATM不依賴於具體的哪一種銀行卡。它只需定義好銀行卡的規格參數(接口),全部實現了這種規格參數的銀行卡都能在ATM上使用。現實生活如此,軟件開發更是如此。依賴倒置原則,它轉換了依賴,高層模塊不依賴於低層模塊的實現,而低層模塊依賴於高層模塊定義的接口。通俗的講,就是高層模塊定義接口,低層模塊負責實現。

Bob Martins對DIP的定義:

高層模塊不該依賴於低層模塊,二者應該依賴於抽象。

抽象不不該該依賴於實現,實現應該依賴於抽象。

 

若是生活中的實例不足以說明依賴倒置原則的重要性,那下面咱們將經過軟件開發的場景來理解爲何要使用依賴倒置原則。

場景一  依賴無倒置(低層模塊定義接口,高層模塊負責實現)

 

從上圖中,咱們發現高層模塊的類依賴於低層模塊的接口。所以,低層模塊須要考慮到全部的接口。若是有新的低層模塊類出現時,高層模塊須要修改代碼,來實現新的低層模塊的接口。這樣,就破壞了開放封閉原則。

 

場景二 依賴倒置(高層模塊定義接口,低層模塊負責實現)

在這個圖中,咱們發現高層模塊定義了接口,將再也不直接依賴於低層模塊,低層模塊負責實現高層模塊定義的接口。這樣,當有新的低層模塊實現時,不須要修改高層模塊的代碼。

由此,咱們能夠總結出使用DIP的優勢:

系統更柔韌:能夠修改一部分代碼而不影響其餘模塊。

系統更健壯:能夠修改一部分代碼而不會讓系統崩潰。

系統更高效:組件鬆耦合,且可複用,提升開發效率。

 

控制反轉(IoC)

DIP是一種 軟件設計原則,它僅僅告訴你兩個模塊之間應該如何依賴,可是它並無告訴如何作。IoC則是一種 軟件設計模式,它告訴你應該如何作,來解除相互依賴模塊的耦合。控制反轉(IoC),它爲相互依賴的組件提供抽象,將依賴(低層模塊)對象的得到交給第三方(系統)來控制即依賴對象不在被依賴模塊的類中直接經過new來獲取。在圖1的例子咱們能夠看到,ATM它自身並無插入具體的銀行卡(工行卡、農行卡等等),而是將插卡工做交給人來控制,即咱們來決定將插入什麼樣的銀行卡來取錢。一樣咱們也經過軟件開發過程當中場景來加深理解。

軟件設計原則:原則爲咱們提供指南,它告訴咱們什麼是對的,什麼是錯的。它不會告訴咱們如何解決問題。它僅僅給出一些準則,以便咱們能夠設計好的軟件,避免不良的設計。一些常見的原則,好比DRY、OCP、DIP等。

軟件設計模式:模式是在軟件開發過程當中總結得出的一些可重用的解決方案,它能解決一些實際的問題。一些常見的模式,好比工廠模式、單例模式等等。

作過電商網站的朋友都會面臨這樣一個問題:訂單入庫。假設系統設計初期,用的是SQL Server數據庫。一般咱們會定義一個SqlServerDal類,用於數據庫的讀寫。

?
1
2
3
4
5
6
7
public class SqlServerDal
{
      public void Add()
     {
         Console.WriteLine( "在數據庫中添加一條訂單!" );
     }
}

 而後咱們定義一個Order類,負責訂單的邏輯處理。因爲訂單要入庫,須要依賴於數據庫的操做。所以在Order類中,咱們須要定義SqlServerDal類的變量並初始化。

?
1
2
3
4
5
6
7
8
9
public class Order
{
         private readonly SqlServerDal dal = new SqlServerDal(); //添加一個私有變量保存數據庫操做的對象
 
          public void Add()
        {
            dal.Add();
        }
}

最後,咱們寫一個控制檯程序來檢驗成果。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DIPTest
{
     class Program
     {
         static void Main( string [] args)
         {
             Order order = new Order();
             order.Add();
 
             Console.Read();
         }
     }
}

 輸出結果:

  OK,結果看起來挺不錯的!正當你沾沾自喜的時候,這時BOSS過來了。「小劉啊,剛客戶那邊打電話過來講數據庫要改爲Access」,「對你來講,應當小CASE啦!」BOSS又補充道。帶着自豪而又糾結的情緒,咱們思考着修改代碼的思路。

 因爲換成了Access數據庫,SqlServerDal類確定用不了了。所以,咱們須要新定義一個AccessDal類,負責Access數據庫的操做。

?
1
2
3
4
5
6
7
public class AccessDal
{
     public void Add()
    {
        Console.WriteLine( "在ACCESS數據庫中添加一條記錄!" );
    }
}

 而後,再看Order類中的代碼。因爲,Order類中直接引用了SqlServerDal類的對象。因此還須要修改引用,換成AccessDal對象。

?
1
2
3
4
5
6
7
8
9
public class Order
{
         private readonly AccessDal dal = new AccessDal(); //添加一個私有變量保存數據庫操做的對象
 
          public void Add()
        {
            dal.Add();
        }
}

輸出結果:

費了九牛二虎之力,程序終於跑起來了!試想一下,若是下次客戶要換成MySql數據庫,那咱們是否是還得從新修改代碼?

顯然,這不是一個良好的設計,組件之間高度耦合,可擴展性較差,它違背了DIP原則。高層模塊Order類不該該依賴於低層模塊SqlServerDal,AccessDal,二者應該依賴於抽象。那麼咱們是否能夠經過IoC來優化代碼呢?答案是確定的。IoC有2種常見的實現方式:依賴注入和服務定位。其中,依賴注入使用最爲普遍。下面咱們將深刻理解依賴注入(DI),並學會使用。

 

依賴注入(DI)

控制反轉(IoC)一種重要的方式,就是將依賴對象的建立和綁定轉移到被依賴對象類的外部來實現。在上述的實例中,Order類所依賴的對象SqlServerDal的建立和綁定是在Order類內部進行的。事實證實,這種方法並不可取。既然,不能在Order類內部直接綁定依賴關係,那麼如何將SqlServerDal對象的引用傳遞給Order類使用呢?

 

 

依賴注入(DI),它提供一種機制,將須要依賴(低層模塊)對象的引用傳遞給被依賴(高層模塊)對象。經過DI,咱們能夠在Order類的外部將SqlServerDal對象的引用傳遞給Order類對象。那麼具體是如何實現呢?

方法一 構造函數注入

構造函數函數注入,毫無疑問經過構造函數傳遞依賴。所以,構造函數的參數必然用來接收一個依賴對象。那麼參數的類型是什麼呢?具體依賴對象的類型?仍是一個抽象類型?根據DIP原則,咱們知道高層模塊不該該依賴於低層模塊,二者應該依賴於抽象。那麼構造函數的參數應該是一個抽象類型。咱們再回到上面那個問題,如何將SqlServerDal對象的引用傳遞給Order類使用呢

首選,咱們須要定義SqlServerDal的抽象類型IDataAccess,並在IDataAccess接口中聲明一個Add方法。

?
1
2
3
4
public interface IDataAccess
{
         void Add();
}

 而後在SqlServerDal類中,實現IDataAccess接口。

?
1
2
3
4
5
6
7
public class SqlServerDal:IDataAccess
{
        public void Add()
        {
            Console.WriteLine( "在數據庫中添加一條訂單!" );
        }
}

 接下來,咱們還須要修改Order類。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   public class Order
   {
         private IDataAccess _ida; //定義一個私有變量保存抽象
 
         //構造函數注入
         public Order(IDataAccess ida)
         {
             _ida = ida; //傳遞依賴
       }
 
         public void Add()
         {
             _ida.Add();
         }
}

 OK,咱們再來編寫一個控制檯程序。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DIPTest
{
     class Program
     {
         static void Main( string [] args)
         {
             SqlServerDal dal = new SqlServerDal(); //在外部建立依賴對象
             Order order = new Order(dal); //經過構造函數注入依賴
 
             order.Add();
 
             Console.Read();
         }
     }
}

 輸出結果:

從上面咱們能夠看出,咱們將依賴對象SqlServerDal對象的建立和綁定轉移到Order類外部來實現,這樣就解除了SqlServerDal和Order類的耦合關係。當咱們數據庫換成Access數據庫時,只需定義一個AccessDal類,而後外部從新綁定依賴,不須要修改Order類內部代碼,則可實現Access數據庫的操做。

定義AccessDal類:

?
1
2
3
4
5
6
7
public class AccessDal:IDataAccess
{
         public void Add()
         {
             Console.WriteLine( "在ACCESS數據庫中添加一條記錄!" );
         }
}

而後在控制檯程序中從新綁定依賴關係:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DIPTest
{
     class Program
     {
         static void Main( string [] args)
         {
              AccessDal dal = new AccessDal(); //在外部建立依賴對象
                Order order = new Order(dal); //經過構造函數注入依賴
 
                order.Add();
 
             Console.Read();
         }
     }
}

輸出結果:

顯然,咱們不須要修改Order類的代碼,就完成了Access數據庫的移植,這無疑體現了IoC的精妙。

 方法二 屬性注入

顧名思義,屬性注入是經過屬性來傳遞依賴。所以,咱們首先須要在依賴類Order中定義一個屬性:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Order
{
       private IDataAccess _ida; //定義一個私有變量保存抽象
      
         //屬性,接受依賴
         public IDataAccess Ida
        {
            set { _ida = value; }
            get { return _ida; }
        }
 
        public void Add()
        {
            _ida.Add();
        }
}

 而後在控制檯程序中,給屬性賦值,從而傳遞依賴:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DIPTest
{
     class Program
     {
         static void Main( string [] args)
         {
             AccessDal dal = new AccessDal(); //在外部建立依賴對象
             Order order = new Order();
             order.Ida = dal; //給屬性賦值
 
             order.Add();
 
             Console.Read();
         }
     }
}

咱們能夠獲得上述一樣的結果。

 方法三 接口注入

相比構造函數注入和屬性注入,接口注入顯得有些複雜,使用也不常見。具體思路是先定義一個接口,包含一個設置依賴的方法。而後依賴類,繼承並實現這個接口。

首先定義一個接口: 

?
1
2
3
4
public interface IDependent
{
            void SetDependence(IDataAccess ida); //設置依賴項
}

依賴類實現這個接口:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Order : IDependent
  {
      private IDataAccess _ida; //定義一個私有變量保存抽象
 
      //實現接口
      public void SetDependence(IDataAccess ida)
      {
          _ida = ida;
      }
 
      public void Add()
      {
          _ida.Add();
      }
 
  }

  控制檯程序經過SetDependence方法傳遞依賴:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DIPTest
{
     class Program
     {
         static void Main( string [] args)
         {
             AccessDal dal = new AccessDal(); //在外部建立依賴對象
           Order order = new Order();
 
             order.SetDependence(dal); //傳遞依賴
 
             order.Add();
 
             Console.Read();
         }
     }
}

咱們一樣能獲得上述的輸出結果。

 

IoC容器

前面全部的例子中,咱們都是經過手動的方式來建立依賴對象,並將引用傳遞給被依賴模塊。好比:

?
1
2
SqlServerDal dal = new SqlServerDal(); //在外部建立依賴對象
Order order = new Order(dal); //經過構造函數注入依賴

 對於大型項目來講,相互依賴的組件比較多。若是還用手動的方式,本身來建立和注入依賴的話,顯然效率很低,並且每每還會出現不可控的場面。正因如此,IoC容器誕生了。IoC容器其實是一個DI框架,它能簡化咱們的工做量。它包含如下幾個功能:

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

目前,比較流行的Ioc容器有如下幾種:

1. Ninjecthttp://www.ninject.org/

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

3. Autofachttp://code.google.com/p/autofac/

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

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

注:根據園友 徐少俠 的提醒,MEF不該該是IoC容器。我又查閱了一些資料,以爲MEF做爲IoC容器是有點勉強,它的主要做用仍是用於應用程序擴展,避免生成脆弱的硬依賴項。

 6. MEFhttp://msdn.microsoft.com/zh-cn/library/dd460648.aspx 

另外,園友 aixuexi 提出Spring.NET也是比較流行的IoC容器。

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

園友 wdwwtzy 也推薦了一個不錯的IoC容器:

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

 以Ninject爲例,咱們一樣來實現 [方法一 構造函數注入] 的功能。

首先在項目添加Ninject程序集,同時使用using指令引入。 

?
1
using Ninject;

而後,Ioc容器註冊綁定依賴:

?
1
2
3
StandardKernel kernel = new StandardKernel();
 
kernel.Bind<IDataAccess>().To<SqlServerDal>(); //註冊依賴

 接下來,咱們獲取須要的Order對象(注入了依賴對象):

?
1
Order order = kernel.Get<Order>();

 下面,咱們寫一個完整的控制檯程序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
 
namespace DIPTest
{
     class Program
     {
         static void Main( string [] args)
         {
            StandardKernel kernel = new StandardKernel(); //建立Ioc容器
            kernel.Bind<IDataAccess>().To<SqlServerDal>(); //註冊依賴
 
              Order order = kernel.Get<Order>(); //獲取目標對象
 
              order.Add();
            Console.Read();
         }
     }
}

 輸出結果:

使用IoC容器,咱們一樣實現了該功能。

 

 總結

在本文中,我試圖以最通俗的方式講解,但願能幫助你們理解這些概念。下面咱們一塊兒來總結一下:DIP是軟件設計的一種思想,IoC則是基於DIP衍生出的一種軟件設計模式。DI是IoC的具體實現方式之一,使用最爲普遍。IoC容器是DI構造函注入的框架,它管理着依賴項的生命週期以及映射關係。

 

 

出處:http://www.javashuo.com/article/p-ptrhaxoe-bk.html

相關文章
相關標籤/搜索