程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 如何深入理解DIP、IoC、DI及IoC容器

如何深入理解DIP、IoC、DI及IoC容器

編輯:關於.NET

前言

對於大部分小菜來說,當聽到大牛們高談DIP、IoC、DI以及IoC容器等名詞時,有沒有瞬間石化的感覺?其實,這些“高大上”的名詞,理解起來也並不是那麼的難,關鍵在於入門。只要我們入門了,然後循序漸進,假以時日,自然水到渠成。

好吧,我們先初略了解一下這些概念。

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

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

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

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

哦!也許你正為這些陌生的概念而傷透腦筋。不過沒關系,接下來我將為你一一道破這其中的玄機。

依賴倒置原則(DIP)

在講概念之前,我們先看生活中的一個例子。

圖1   ATM與銀行卡

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

Bob Martins對DIP的定義:

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

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

如果生活中的實例不足以說明依賴倒置原則的重要性,那下面我們將通過軟件開發的場景來理解為什麼要使用依賴倒置原則。

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

從上圖中,我們發現高層模塊的類依賴於低層模塊的接口。因此,低層模塊需要考慮到所有的接口。如果有新的低層模塊類出現時,高層模塊需要修改代碼,來實現新的低層模塊的接口。這樣,就破壞了開放封閉原則。

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

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

由此,我們可以總結出使用DIP的優點:

系統更柔韌:可以修改一部分代碼而不影響其他模塊。

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

系統更高效:組件松耦合,且可復用,提高開發效率。

控制反轉(IoC)

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

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

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

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

public class SqlServerDal
{
     public void Add()
    {
        Console.WriteLine("在數據庫中添加一條訂單!");
    }
}

然後我們定義一個Order類,負責訂單的邏輯處理。由於訂單要入庫,需要依賴於數據庫的操作。因此在Order類中,我們需要定義SqlServerDal類的變量並初始化。

public class Order
{
        private readonly SqlServerDal dal = new SqlServerDal();//添加一個私有變量保存數據庫操作的對象
     
         public void Add()
       {
           dal.Add();
       }
}

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

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數據庫的操作。

本欄目

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

定義AccessDal類:

public class AccessDal:IDataAccess
{
        public void Add()
        {
            Console.WriteLine("在ACCESS數據庫中添加一條記錄!");
        }
}

然後在控制台程序中重新綁定依賴關系:

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中定義一個屬性:

public class Order
{
      private IDataAccess _ida;//定義一個私有變量保存抽象
          
        //屬性,接受依賴
        public IDataAccess Ida
       {
           set { _ida = value; }
           get { return _ida; }
       }
     
       public void Add()
       {
           _ida.Add();
       }
}

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

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();
        }
    }
}

我們可以得到上述同樣的結果。

方法三 接口注入

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

首先定義一個接口:

public interface IDependent
{
           void SetDependence(IDataAccess ida);//設置依賴項
}

依賴類實現這個接口:

public class Order : IDependent
 {
     private IDataAccess _ida;//定義一個私有變量保存抽象
     
     //實現接口
     public void SetDependence(IDataAccess ida)
     {
         _ida = ida;
     }
     
     public void Add()
     {
         _ida.Add();
     }
     
 }

控制台程序通過SetDependence方法傳遞依賴:

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容器

前面所有的例子中,我們都是通過手動的方式來創建依賴對象,並將引用傳遞給被依賴模塊。比如:

SqlServerDal dal = new SqlServerDal();//在外部創建依賴對象

Order order = new Order(dal);//通過構造函數注入依賴

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

動態創建、注入依賴對象。

管理對象生命周期。

映射依賴關系。

目前,比較流行的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

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

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

using Ninject;

然後,Ioc容器注冊綁定依賴:

StandardKernel kernel = new StandardKernel();

kernel.Bind<IDataAccess>().To<SqlServerDal>();//注冊依賴

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

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

下面,我們寫一個完整的控制台程序

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://liuhaorain.cnblogs.com

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved