1. 程式人生 > >依賴注入那些事兒【3】 之 依賴注入那些事兒

依賴注入那些事兒【3】 之 依賴注入那些事兒

上面我們從需求背景的角度,講述了依賴注入的來源和定義。但是,如果依賴注入僅僅就只有這麼點東西,那也沒有什麼值得討論的了。但是,上面討論的僅僅是依賴注入的內涵,其外延還是非常廣泛的,從依賴注入衍生出了很多相關的概念與技術,下面我們討論一下依賴注入的“那些事兒”。

3.1 依賴注入的類別

依賴注入有很多種方法,上面看到的例子中,只是其中的一種,下面分別討論不同的依賴注入型別。

3.1.1 Setter注入

第一種依賴注入的方式,就是Setter注入,上面的例子中,將武器注入Role就是Setter注入。正式點說:

Setter注入(Setter Injection)是指在客戶類中,設定一個服務類介面型別的資料成員,並設定一個Set方法作為注入點,這個Set方法接受一個具體的服務類例項為引數,並將它賦給服務類介面型別的資料成員。

圖3.1 Setter注入示意

上圖展示了Setter注入的結構示意圖,客戶類ClientClass設定IServiceClass型別成員_serviceImpl,並設定Set_ServiceImpl方法作為注入點。Context會負責例項化一個具體的ServiceClass,然後注入到ClientClass裡。

下面給出Setter注入的示例程式碼。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    internal interface IServiceClass
    {
        String ServiceInfo();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    internal class ServiceClassA : IServiceClass
    {
        public String ServiceInfo()
        {
            return "我是ServceClassA";
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SetterInjection
{
    internal class ServiceClassB : IServiceClass
    {
        public String ServiceInfo()
        {
            return "我是ServceClassB";
        }
    }
}
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());
        }
    }
}
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();
            client.Set_ServiceImpl(serviceB);
            client.ShowInfo();
        }
    }
}

執行結果如下:

圖3.2 Setter注入執行結果

3.1.2 構造注入

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

構造注入(Constructor Injection)是指在客戶類中,設定一個服務類介面型別的資料成員,並以建構函式為注入點,這個建構函式接受一個具體的服務類例項為引數,並將它賦給服務類介面型別的資料成員。

圖3.3 構造注入示意

圖3.3是構造注入的示意圖,可以看出,與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());
        }
    }
}

可以看到,唯一的變化就是建構函式取代了Set_ServiceImpl方法,成為了注入點。

3.1.3 依賴獲取

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

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

可以看到,這種方法變被動為主動,使得客戶類在需要時主動獲取服務類,而將多型性的實現封裝到獲取點裡面。獲取點可以有很多種實現,也許最容易想到的就是建立一個Simple Factory作為獲取點,客戶類傳入一個指定字串,以獲取相應服務類例項。如果所依賴的服務類是一系列類,那麼依賴獲取一般利用Abstract Factory模式構建獲取點,然後,將服務類多型性轉移到工廠的多型性上,而工廠的型別依賴一個外部配置,如XML檔案。

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

下面給一個具體的例子,現在我們假設有個程式,既可以使用Windows風格外觀,又可以使用Mac風格外觀,而內部業務是一樣的。

圖3.4 依賴獲取示意

上圖乍看有點複雜,不過如果讀者熟悉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();
    }
}
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;
        }
    }
}
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;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace DependencyLocate
{
    internal interface IFactory
    {
        IWindow MakeWindow();
 
        IButton MakeButton();
 
        ITextBox MakeTextBox();
    }
}
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();
        }
    }
}
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();
        }
    }
}
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");
            }
        }
    }
}
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();
        }
    }
}

這裡我們用XML作為配置檔案。配置檔案Config.xml如下:

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

可以看到,這裡我們將配置設定為Mac風格,編譯執行上述程式碼,執行結果如下:

圖3.5 配置Mac風格後的執行結果

現在,我們不動程式,僅僅將配置檔案中的“Mac”改為Windows,執行後結果如下:

圖3.6 配置為Windows風格後的執行結果

從執行結果看出,我們僅僅通過修改配置檔案,就改變了整個程式的行為(我們甚至沒有重新編譯程式),這就是多型性的威力,也是依賴注入效果。

3.2 反射與依賴注入

回想上面Dependency Locate的例子,我們雖然使用了多型性和Abstract Factory,但對OCP貫徹的不夠徹底。在理解這點前,朋友們一定要注意潛在擴充套件在哪裡,潛在會出現擴充套件的地方是“新的元件系列”而不是“元件種類”,也就是說,這裡我們假設元件就三種,不會增加新的元件,但可能出現新的外觀系列,如需要加一套Ubuntu風格的元件,我們可以新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,並分別實現相應介面,這是符合OCP的,因為這是擴充套件。但我們除了修改配置檔案,還要無可避免的修改FactoryContainer,需要加一個分支條件,這個地方破壞了OCP。依賴注入本身是沒有能力解決這個問題的,但如果語言支援反射機制(Reflection),則這個問題就迎刃而解。

我們想想,現在的難點是出在這裡:物件最終還是要通過“new”來例項化,而“new”只能例項化當前已有的類,如果未來有新類新增進來,必須修改程式碼。如果,我們能有一種方法,不是通過“new”,而是通過類的名字來例項化物件,那麼我們只要將類的名字作為配置項,就可以實現在不修改程式碼的情況下,載入未來才出現的類。所以,反射給了語言“預見未來”的能力,使得多型性和依賴注入的威力大增。

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

圖3.7 引入反射機制的Dependency Locate

可以看出,引入反射機制後,結構簡單了很多,一個反射工廠代替了以前的一堆工廠,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;
        }
    }
}

配置檔案如下:

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

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

3.3 多型的活性與依賴注入

3.3.1 多型性的活性

這一節我們討論多型的活性及其與依賴注入型別選擇間密切的關係。

首先說明,“多型的活性”這個術語是我個人定義的,因為我沒有找到既有的概念名詞可以表達我的意思,所以就自己造了一個詞。這裡,某多型的活性是指被此多型隔離的變化所發生變化的頻繁程度,頻繁程度越高,則活性越強,反之亦然。

上文說過,多型性可以隔離變化,但是,不同的變化,發生的頻率是不一樣的,這就使得多型的活性有所差別,這種差別影響了依賴注入的型別選擇。

舉例來說,本文最開始提到的武器多型性,其活性非常高,因為在那個程式中,Role在一次執行中可能更換多次武器。而現在我們假設Role也實現了多型性,這是很可能的,因為在遊戲中,不同型別的角色(如暗夜精 靈、牛頭人、矮人等)很多屬性和業務是想通的,所以很可能通過一個IRole或AbstractRole抽象類實現多型性,不過,Role在例項化後(一般在使用者登入成功後),是不會變化的,很少有遊戲允許同一個玩家在執行中變換Role型別,所以Role應該是一但例項化,就不會變化,但如果再例項化一個(如另一個玩家登入),則可能就變化了。最後,還有一種多型性是活性非常低的,如我們熟悉的資料訪問層多型性,即使我們實現了SQL Server、Oracle和Access等多種資料庫的訪問層,並實現了依賴注入,但幾乎遇不到程式執行著就改資料庫或短期內資料庫頻繁變動的情況。

以上不同的多型性,不但特徵不同,其目的一般也不同,總結如下:

高活多型性——指在客戶類例項執行期間,服務類可能會改變的多型性。

中活多型性——指在客戶類例項化後,服務類不會改變,但同一時間記憶體在的不同例項可能擁有不同型別的服務類。

低活多型性——指在客戶類例項化後,服務類不會改變,且同一時間內所有客戶類都擁有相同型別的服務類。

以上三種多型性,比較好的例子就是上文提到的武器多型性(高活)、角色多型性(中活)和資料訪問層多型性(低活)。另外,我們說一種多型性是空間穩定的,如果同一客戶類在同一時間內的所有例項都依賴相同型別的服務類,反之則叫做空間不穩定多型性。我們說一種多型性是時間穩定的,如果一個客戶類在例項化後,所以來的服務類不能再次更改,反之則叫做時間不穩定多型性。顯然,高活多型性時間和空間均不穩定;中活多型性是時間穩定的,但空間不穩定;低活多型性時間空間均穩定。

3.3.2 不同活性多型的依賴注入選擇

一般來說,高活多型性適合使用Setter注入。因為Setter注入最靈活,也是唯一允許在同一客戶類例項執行期間更改服務類的注入方式。並且這種注入一般由上下文環境通過Setter的引數指定服務類型別,方便靈活,適合頻繁變化的高活多型性。

對於中活多型性,則適合使用Constructor注入。因為Constructor注入也是由上下文環境通過Construtor的引數指定服務類型別,但一點客戶類例項化後,就不能進行再次注入,保證了其時間穩定性。

而對於低活多型性,則適合使用Dependency Locate並配合檔案配置進行依賴注入,或Setter、Constructor配合配置檔案注入,因為依賴源來自檔案,如果要更改服務類,則需要更改配置檔案,一則確保了低活多型性的時間和空間穩定性,二是更改配置檔案的方式方便於大規模服務類替換。(因為低活多型性一旦改變行為,往往規模很大,如替換整個資料訪問層,如果使用Setter和Construtor傳參,程式中需要改變的地方不計其數)

本質上,這種選擇是因為不同的依賴注入型別有著不同的穩定性,大家可以細細體會“活性”、“穩定性”和“依賴注入型別”之間密切的關係。

Creative Commons License

本文基於署名-非商業性使用 3.0許可協議釋出,歡迎轉載,演繹,但是必須保留本文的署名張洋(包含連結),且不得用於商業目的。如您有任何疑問或者授權方面的協商,請與我聯絡

相關推薦

依賴注入那些事兒3 依賴注入那些事兒

上面我們從需求背景的角度,講述了依賴注入的來源和定義。但是,如果依賴注入僅僅就只有這麼點東西,那也沒有什麼值得討論的了。但是,上面討論的僅僅是依賴注入的內涵,其外延還是非常廣泛的,從依賴注入衍生出了很多相關的概念與技術,下面我們討論一下依賴注入的“那些事兒”。 3.1 依賴

依賴注入那些事兒4 IoC Container

4.1 IoC Container出現的必然性 上面討論了諸多依賴注入的話題。說道依賴注入,就不能不說IoC Container(IoC容器),那麼到底什麼是IoC容器?我們還是先來看看它的出現背景。 我們知道,軟體開發領域有句著名的論斷:不要重複發明輪子!因為軟體開發講求

kindle筆記 《明朝那些事兒》-2018-7-1

不下來 正是 不知道 微信 後來 真的 還在 精品 毫無 最近在讀這本書。之前在微信讀書裏斷斷續續讀過,讀到深處還想蹦起來做筆記那種。後來種種原因斷了,再沒續上。 現在又開始啦。最近還在重八兄造反階段,還很早呢,有時候晚上玩手機停不下來的時候會強迫自己讀一會兒,加油。 而且

循序漸進學.Net Core Web Api開發系列11依賴注入

系列目錄 一、概述 本篇介紹如何採用依賴注入的方式建立和使用物件,主要從應用層面進行描述,不涉及具體的內部原理。 二、演練 假設要做一個日誌服務的類,它實現在控制檯打印出帶時間資訊的日誌資訊。 首先定義該服務的介面與實現類。 public interface ILogSe

PHP4個反斜槓、3個反斜槓的情況

背景: 今天在學習正則表示式,遇到了一個問題,php中4個反斜槓\在不同直譯器中的結果。 正文: 我們先來看看以下程式碼 echo '\';   執行之後報語法錯誤,提示unexpected'\'; ',即\'; 這幾個字元出了問題 Pars

ZooKeeper學習配置3網路配置

下面這些配置對client與server之間的連線和超時時間進行了限制。 globalOutstandingLimit 這個配置指定了等待處理的最大請求數量的限制(zookeeper.globalOutstandingLimit)。 client傳送請求的速度可能會比server端處理的速度快,會導致請求

SpringSpring依賴注入與控制反轉理解

Spring是一個龐大的框架,封裝了很多成熟的功能,能夠讓我們無需重複造輪子;其次,它使用IOC進行依賴管理,利用JAVA的反射機制,將例項的初始化交給Spring,Spring可以通過配置檔案管理例項,我們就不用自己初始化例項啦。 有人會問 “那我們可以直接

Android中JNI程式設計的那些事兒

後續可能為需要加入一些特定的模組到android中,所以JNI還需繼續熟悉起來 首先說明,Android系統不允許一個純粹使用C/C++的程式出現,它要求必須是通過Java程式碼嵌入Native C/C++——即通過JNI的方式來使用本地(Native)程式碼。因此

Webpack 下載安裝3.8.1(Windows10)

平臺: window10 npm:8.9.4 ps:之前按照官網(官網的文件沒編寫好),然後各種報錯,最後用3.8.1版本 (1)預備 (2)安裝we

Webpack CSS單獨打包(webpack 3.8.1)

(1)預備備 ① webpack3.8.1 安裝 ② 包依賴 var glob = require(‘globby’); npm install globby

MVVM- AngularJS 依賴注入

依賴注入(Dependency Injection):,一個或更多的依賴(或服務)

Erlang那些事兒3我是函式(fun),萬物源MFA

  Erlang程式碼到處都是模式匹配,這把屠龍刀可是Erlang的看家本領、獨家絕學,之前在《Erlang那些事兒第1回之我是變數,一次賦值永不改變》文章提到過,Erlang一切皆是模式匹配。從變數的賦值、函式的形參傳遞、過載函式的應用,到處都有模式匹配的影子,剛開始寫程式碼會感覺不習慣,但是當你用習慣之後

手機管理應用研究3—— 垃圾清理篇

dma 系統垃圾 存在 分析 獲得 /dev/ 進行 指定 相互 歡迎轉載。轉載請註明:http://blog.csdn.net/zhgxhuaa 說明 在總篇中提到過垃圾清理,本篇將著重介紹針對緩存、卸載殘留、無用數據等“靜態內容”的清理,有關於系統進程的清理以

js 技巧 (十)廣告JS代碼效果大全 3

lpad log gid cond absolute dex offset ima disable 3.[允許關閉] 與前面兩個代碼不同的是,廣告圖下方增加了一個圖片按紐,允許訪客點擊關閉廣告圖片,下面文本框中就是實現效果所需代碼: var delta=0.015;

rediscentos6.x安裝redis3.0.x

local con releases 新建 zxvf 分享 執行 .tar.gz all centos6.9_x86_64 1、下載redis安裝包 http://download.redis.io/releases/redis-3.2.9.tar.gz 2、解壓 tar

AMQJMS Mesage structure(JMS消息結構)

api 兼容 ctu 標識 提供商 nbsp 連接 特定 ext Δ消息體:JMS API 定義了5種消息格式也叫消息類型,可以使用不同形式發送和接收數據,並可以兼容現有的消息格式 TextMessage,MapMessage,ByteMessage,StreamMessa

javajava代碼的執行機制

() alt 分享 str clas not roc 成員 輸入 要在JVM中執行java代碼必須要編譯為class文件,JDK是如何將Java代碼編譯為class文件,這種機制通常被稱為Java源碼編譯機制。 1、JVM定義了class文件的格式,但是並沒有定義如何將ja

springbootstarter pom

soc jet rep uri shel mvc must batch god SpringBoot針對不同業務提供了不同的starter pom,根據springboot版本不同可能有差異。 spring-boot-starter springboot核心start

VSTO:使用C#開發Excel、Word3

定義 應用程序 導致 編程 生活 成功 員工 無法使用 我們 前言在2002年,Visual Studio .NET和.NET Framework的第一個版本即將完成。我們中的幾個意識到微軟的.NET將會錯過Office,除非我們做了一些事情。 以前曾經是Visual Ba

MantisBT在linux環境上的安裝搭建

bts mantisbt 在工作中,選用了MantisBT作為公司的BTS工具。MantisBT的作為一款缺陷跟蹤管理系統,有以下優點:開源、 免費;可與開源的testlink集成,便於統計bug的用例發現率;具有bug關聯功能;權限設置靈活,不同角色有不同權限;具有郵件通知功能,每個用戶可根據自身的工