C# 依賴注入


一、什么是依賴注入

依賴注入的正式定義:

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

二、依賴注入的類別

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

下面給出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

運行結果如下:

2.構造注入

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

構造注入(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等)的設計提供了可能性。

 


免責聲明!

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



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