C# 依賴注入

1、什麼是依賴注入 框架

依賴注入的正式定義:ide

依賴注入(Dependency Injection),是這樣一個過程:因爲某客戶類只依賴於服務類的一個接口,而不依賴於具體服務類,因此客戶類只定義一個注入點。在程序運行過程當中,客戶類不直接實例化具體服務類實例,而是客戶類的運行上下文環境或專門組件負責實例化服務類,而後將其注入到客戶類中,保證客戶類的正常運行。函數

2、依賴注入的類別this

1.Setter注入
Setter注入(Setter Injection)是指在客戶類中,設置一個服務類接口類型的數據成員,並設置一個Set方法做爲注入點,這個Set方法接受一個具體的服務類實例爲參數,並將它賦給服務類接口類型的數據成員。
spa

下面給出Setter注入的示例代碼。設計

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace SetterInjection
{
    internal interface IServiceClass
    {
        String ServiceInfo();
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace SetterInjection
{
    internal class ServiceClassA : IServiceClass
    {
        public String ServiceInfo()
        {
            return "我是ServceClassA";
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace SetterInjection
{
    internal class ServiceClassB : IServiceClass
    {
        public String ServiceInfo()
        {
            return "我是ServceClassB";
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace SetterInjection
{
    internal class ClientClass
    {
    //注入點
        private IServiceClass _serviceImpl;
    //客戶類中的方法,初始化注入點  
        public void Set_ServiceImpl(IServiceClass serviceImpl)
        {
            this._serviceImpl = serviceImpl;
        }
  
        public void ShowInfo()
        {
            Console.WriteLine(_serviceImpl.ServiceInfo());
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace SetterInjection
{
    class Program
    {
        static void Main(string[] args)
        {
            IServiceClass serviceA = new ServiceClassA();
            IServiceClass serviceB = new ServiceClassB();
            ClientClass client = new ClientClass();
  
            client.Set_ServiceImpl(serviceA);
            client.ShowInfo();//結果:我是ServceClassA
            client.Set_ServiceImpl(serviceB);
            client.ShowInfo();//結果:我是ServceClassB

            Console.ReadLine();
        }
    }
}
View Code

運行結果以下:3d

2.構造注入code

另一種依賴注入方式,是經過客戶類的構造函數,向客戶類注入服務類實例。xml

構造注入(Constructor Injection)是指在客戶類中,設置一個服務類接口類型的數據成員,並以構造函數爲注入點,這個構造函數接受一個具體的服務類實例爲參數,並將它賦給服務類接口類型的數據成員。對象

與Setter注入很相似,只是注入點由Setter方法變成了構造方法。這裏要注意,因爲構造注入只能在實例化客戶類時注入一次,因此一點注入,程序運行期間是無法改變一個客戶類對象內的服務類實例的。

因爲構造注入和Setter注入的IServiceClass,ServiceClassA和ServiceClassB是同樣的,因此這裏給出另外ClientClass類的示例代碼。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace ConstructorInjection
{
    internal class ClientClass
    {
        private IServiceClass _serviceImpl;
  
        public ClientClass(IServiceClass serviceImpl)
        {
            this._serviceImpl = serviceImpl;
        }
  
        public void ShowInfo()
        {
            Console.WriteLine(_serviceImpl.ServiceInfo());
        }
    }
}
View Code

能夠看到,惟一的變化就是構造函數取代了Set_ServiceImpl方法,成爲了注入點。

3. 依賴獲取

上面提到的注入方式,都是客戶類被動接受所依賴的服務類,這也符合「注入」這個詞。不過還有一種方法,能夠和依賴注入達到相同的目的,就是依賴獲取。

依賴獲取(Dependency Locate)是指在系統中提供一個獲取點,客戶類仍然依賴服務類的接口。當客戶類須要服務類時,從獲取點主動取得指定的服務類,具體的服務類類型由獲取點的配置決定。

能夠看到,這種方法變被動爲主動,使得客戶類在須要時主動獲取服務類,而將多態性的實現封裝到獲取點裏面。獲取點能夠有不少種實現,也許最容易想到的就是創建一個Simple Factory做爲獲取點,客戶類傳入一個指定字符串,以獲取相應服務類實例。若是所依賴的服務類是一系列類,那麼依賴獲取通常利用Abstract Factory模式構建獲取點,而後,將服務類多態性轉移到工廠的多態性上,而工廠的類型依賴一個外部配置,如XML文件。

不過,不論使用Simple Factory仍是Abstract Factory,都避免不了判斷服務類類型或工廠類型,這樣系統中總要有一個地方存在不符合OCP的if…else或switch…case結構,這種缺陷是Simple Factory和Abstract Factory以及依賴獲取自己沒法消除的,而在某些支持反射的語言中(如C#),經過將反射機制的引入完全解決了這個問題(後面討論)。

下面給一個具體的例子,如今咱們假設有個程序,既可使用Windows風格外觀,又可使用Mac風格外觀,而內部業務是同樣的。

上圖乍看有點複雜,不過若是讀者熟悉Abstract Factory模式,應該能很容易看懂,這就是Abstract Factory在實際中的一個應用。這裏的Factory Container做爲獲取點,是一個靜態類,它的「Type構造函數」依據外部的XML配置文件,決定實例化哪一個工廠。下面仍是來看示例代碼。因爲不一樣組件的代碼是類似的,這裏只給出Button組件的示例代碼,完整代碼請參考文末附上的完整源程序。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace DependencyLocate
{
    internal interface IButton
    {
        String ShowInfo();
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace DependencyLocate
{
    internal sealed class WindowsButton : IButton
    {
        public String Description { get; private set; }
  
        public WindowsButton()
        {
            this.Description = "Windows風格按鈕";
        }
  
        public String ShowInfo()
        {
            return this.Description;
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace DependencyLocate
{
    internal sealed class MacButton : IButton
    {
        public String Description { get; private set; }
  
        public MacButton()
        {
            this.Description = " Mac風格按鈕";
        }
  
        public String ShowInfo()
        {
            return this.Description;
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace DependencyLocate
{
    internal interface IFactory
    {
        IWindow MakeWindow();
  
        IButton MakeButton();
  
        ITextBox MakeTextBox();
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace DependencyLocate
{
    internal sealed class WindowsFactory : IFactory
    {
        public IWindow MakeWindow()
        {
            return new WindowsWindow();
        }
  
        public IButton MakeButton()
        {
            return new WindowsButton();
        }
  
        public ITextBox MakeTextBox()
        {
            return new WindowsTextBox();
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace DependencyLocate
{
    internal sealed class MacFactory : IFactory
    {
        public IWindow MakeWindow()
        {
            return new MacWindow();
        }
  
        public IButton MakeButton()
        {
            return new MacButton();
        }
  
        public ITextBox MakeTextBox()
        {
            return new MacTextBox();
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
  
namespace DependencyLocate
{
    internal static class FactoryContainer
    {
        public static IFactory factory { get; private set; }
  
        static FactoryContainer()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load("http://www.cnblogs.com/Config.xml");
            XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0];
  
            if ("Windows" == xmlNode.Value)
            {
                factory = new WindowsFactory();
            }
            else if ("Mac" == xmlNode.Value)
            {
                factory = new MacFactory();
            }
            else
            {
                throw new Exception("Factory Init Error");
            }
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
  
namespace DependencyLocate
{
    class Program
    {
        static void Main(string[] args)
        {
            IFactory factory = FactoryContainer.factory;
            IWindow window = factory.MakeWindow();
            Console.WriteLine("建立 " + window.ShowInfo());
            IButton button = factory.MakeButton();
            Console.WriteLine("建立 " + button.ShowInfo());
            ITextBox textBox = factory.MakeTextBox();
            Console.WriteLine("建立 " + textBox.ShowInfo());
  
            Console.ReadLine();
        }
    }
}
View Code

這裏咱們用XML做爲配置文件。配置文件Config.xml以下:

<?xml version="1.0" encoding="utf-8" ?>
<config>
    <factory>Mac</factory>
</config>
View Code

能夠看到,這裏咱們將配置設置爲Mac風格,編譯運行上述代碼,運行結果以下:

配置Mac風格後的運行結果

如今,咱們不動程序,僅僅將配置文件中的「Mac」改成Windows,運行後結果以下:

配置爲Windows風格後的運行結果

從運行結果看出,咱們僅僅經過修改配置文件,就改變了整個程序的行爲(咱們甚至沒有從新編譯程序),這就是多態性的威力,也是依賴注入效果。

反射與依賴注入

回想上面Dependency Locate的例子,咱們雖然使用了多態性和Abstract Factory,但對OCP貫徹的不夠完全。在理解這點前,朋友們必定要注意潛在擴展在哪裏,潛在會出現擴展的地方是「新的組件系列」而不是「組件種類」,也就是說,這裏咱們假設組件就三種,不會增長新的組件,但可能出現新的外觀系列,如須要加一套Ubuntu風格的組件,咱們能夠新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,並分別實現相應接口,這是符合OCP的,由於這是擴展。但咱們除了修改配置文件,還要無可避免的修改FactoryContainer,須要加一個分支條件,這個地方破壞了OCP。依賴注入自己是沒有能力解決這個問題的,但若是語言支持反射機制(Reflection),則這個問題就迎刃而解。

咱們想一想,如今的難點是出在這裏:對象最終仍是要經過「new」來實例化,而「new」只能實例化當前已有的類,若是將來有新類添加進來,必須修改代碼。若是,咱們能有一種方法,不是經過「new」,而是經過類的名字來實例化對象,那麼咱們只要將類的名字做爲配置項,就能夠實如今不修改代碼的狀況下,加載將來纔出現的類。因此,反射給了語言「預見將來」的能力,使得多態性和依賴注入的威力大增。

下面是引入反射機制後,對上面例子的改進:

能夠看出,引入反射機制後,結構簡單了不少,一個反射工廠代替了之前的一堆工廠,Factory Container也不須要了。並且之後有新組件系列加入時,反射工廠是不用改變的,只需改變配置文件就能夠完成。下面給出反射工廠和配置文件的代碼。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml;
  
namespace DependencyLocate
{
    internal static class ReflectionFactory
    {
        private static String _windowType;
        private static String _buttonType;
        private static String _textBoxType;
  
        static ReflectionFactory()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load("http://www.cnblogs.com/Config.xml");
            XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0];
  
            _windowType = xmlNode.ChildNodes[0].Value;
            _buttonType = xmlNode.ChildNodes[1].Value;
            _textBoxType = xmlNode.ChildNodes[2].Value;
        }
  
        public static IWindow MakeWindow()
        {
            return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _windowType) as IWindow;
        }
  
        public static IButton MakeButton()
        {
            return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _buttonType) as IButton;
        }
  
        public static ITextBox MakeTextBox()
        {
            return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _textBoxType) as ITextBox;
        }
    }
}
View Code

配置文件以下:

<?xml version="1.0" encoding="utf-8" ?>
<config>
    <window>MacWindow</window>
    <button>MacButton</button>
    <textBox>MacTextBox</textBox>
</config>
View Code

反射不只能夠與Dependency Locate結合,也能夠與Setter Injection與Construtor Injection結合。反射機制的引入,下降了依賴注入結構的複雜度,使得依賴注入完全符合OCP,併爲通用依賴注入框架(如Spring.NET中的IoC部分、Unity等)的設計提供了可能性。

相關文章
相關標籤/搜索