1. 程式人生 > >C#依賴注入例項解析

C#依賴注入例項解析

1.5 實現依賴注入1.5.1 背景介紹

設計模式中,尤其是結構型模式很多時候解決的就是物件間的依賴關係,變依賴具體為依賴抽象。平時開發中如果發現客戶程式依賴某個(或某類)物件,我們常常會對它們進行一次抽象,形成抽象的抽象類、介面,這樣客戶程式就可以擺脫所依賴的具體型別。

這個過程中有個環節被忽略了——誰來選擇客戶程式需要的滿足抽象型別的具體型別呢?通過後面的介紹你會發現很多時候建立型模式可以比較優雅地解決這個問題。但另一問題出現了,如果您設計的不是具體業務邏輯,而是公共庫或框架程式,這時候您是一個“服務方”,不是您呼叫那些構造型別,而是它們把抽象型別傳給您,怎麼鬆散地把加工好的抽象型別傳遞給客戶程式就是另一回事了。

    這個情形也就是常說的“控制反轉”,IOC:Inverse of Control;框架程式與抽象型別的呼叫關係就像常說的好萊塢規則:Don’t call me, I’ll call you.

參考Martin Fowler在《Inversion of Control Containers and the Dependency Injection pattern》一文,我們可以採用“依賴注入”的方式將加工好的抽象型別例項“注入”到客戶程式中,本書的示例也將大量採用這種方式將各種依賴項“注入”到模式實現的外部——客戶程式。下面我們結合一個具體的示例看看為什麼需要依賴注入,以及Martin Fowler文中提到的三種經典方式,然後依據C#語言的特質,再擴展出一個基於Attribter方式注入(參考原有的Setter命名,這裡將基於Attribute的方法稱為Attributer)。

1.5.2 示例情景

客戶程式需要一個提供System.DateTime型別當前系統時間的物件,然後根據需要僅僅把其中的年份部分提取出來,因此最初的實現程式碼如下:

C#

using System;

using System.Diagnostics;

namespace MarvellousWorks.PracticalPattern.Concept.DependencyInjection.Example1

{

    class TimeProvider

    {

        public DateTime CurrentDate { get { return DateTime.Now; } }

    }

    public class Client

    {

        public int GetYear()

        {

            TimeProvider timeProvier = new TimeProvider();

            return timeProvier.CurrentDate.Year;

        }

    }

}

後來因為某種原因,發現使用.NET Framework自帶的日期型別精度不夠,需要提供其他來源的TimeProvider,確保在不同精度要求的功能模組中使用不同的TimeProvider。這樣問題集中在TimeProvider的變化會影響客戶程式,但其實客戶程式僅需要抽象地使用其獲取當前時間的方法。為此,增加一個抽象介面,確保客戶程式僅依賴這個介面ITimeProvider,由於這部分客戶程式僅需要精確到年,因此它可以使用一個名為SystemTimeProvider (:ITimeProvider)的型別。新的實現程式碼如下:

C#

using System;

namespace 
   MarvellousWorks.PracticalPattern.Concept.DependencyInjection.Example2

{

    interface ITimeProvider

    {

        DateTime CurrentDate { get;}

    }

    class TimeProvider : ITimeProvider

    {

        public DateTime CurrentDate { get { return DateTime.Now; } }

    }

    public class Client

    {

        public int GetYear()

        {

            ITimeProvider timeProvier = new TimeProvider();

            return timeProvier.CurrentDate.Year;

        }

    }

}

這樣看上去客戶程式後續處理權都依賴於抽象的ITimeProvider,問題似乎解決了?沒有,它還要知道具體的SystemTimeProvider。因此,需要增加一個物件,由它選擇某種方式把ITimeProvider例項傳遞給客戶程式,這個物件被稱為Assembler。新的結構如圖1-19所示。

圖1-19 增建裝配物件後新的依賴關係

其中,Assembler的職責如下:

         知道每個具體TimeProviderImpl的型別。

         可根據客戶程式的需要,將抽象ITimeProvider反饋給客戶程式。

         本身還負責對TimeProviderImpl的建立。

下面是一個Assembler的示例實現:

C#

public class Assembler

{

    ///

    /// 儲存“抽象型別/實體型別”對應關係的字典

    ///

    private static Dictionary dictionary = 
       new Dictionary();

    static Assembler()

    {

        // 註冊抽象型別需要使用的實體型別

        // 實際的配置資訊可以從外層機制獲得,例如通過配置定義

        dictionary.Add(typeof(ITimeProvider), typeof(SystemTimeProvider));

    }

    /// 根據客戶程式需要的抽象型別選擇相應的實體型別,並返回型別例項

    /// 實體型別例項

    public object Create(Type type)     // 主要用於非泛型方式呼叫

    {

        if ((type == null) || !dictionary.ContainsKey(type)) throw new 
            NullReferenceException();

        Type targetType = dictionary[type];

        return Activator.CreateInstance(targetType);

    }

    /// 抽象型別(抽象類/介面/或者某種基類)

    public T Create()    // 主要用於泛型方式呼叫

    {

        return (T)Create(typeof(T));

    }

}

1.5.3 Constructor注入

建構函式注入,顧名思義,就是在建構函式的時候,通過Assembler或其他機制把抽象型別作為引數傳遞給客戶型別。這種方式雖然相對其他方式有些粗糙,而且僅在構造過程中通過“一錘子”方式設定好,但很多時候我們設計上正好就需要這種Read Only的注入方式。其實現方式如下:

C#

/// 在建構函式中注入

class Client

{

    private ITimeProvider timeProvider;

    public Client(ITimeProvider timeProvider)

    {

        this.timeProvider = timeProvider;

    }

}

Unit Test

[TestClass]

public class TestClient

{

    [TestMethod]

    public void Test()

    {

        ITimeProvider timeProvider = 
           (new Assembler()).Create();

        Assert.IsNotNull(timeProvider);     // 確認可以正常獲得抽象型別例項

        Client client = new Client(timeProvider);   // 在建構函式中注入

    }

}

1.5.4 Setter注入

Setter注入是通過屬性賦值的辦法解決的,由於Java等很多語言中沒有真正的屬性,所以Setter注入一般通過一個set()方法實現,C#語言由於本身就有可寫屬性,所以實現起來更簡潔,更像Setter。相比較Constructor方式而言,Setter給了客戶型別後續修改的機會,它比較適應於客戶型別例項存活時間較長,但Assembler修改抽象型別指定的具體實體型別相對較快的情景;不過也可以由客戶程式根據需要動態設定所需的型別。其實現方式如下:

C#

/// 通過Setter實現注入

class Client

{

    private ITimeProvider timeProvider;

    public ITimeProvider TimeProvider

    {

        get { return this.timeProvider; }   // getter本身和以Setter方式實現
                                                       注入沒有關係

        set { this.timeProvider = value; } // Setter

    }

}

Unit Test

[TestClass]

public class TestClient

{

    [TestMethod]

    public void Test()

    {

        ITimeProvider timeProvider = 
           (new Assembler()).Create();

         Assert.IsNotNull(timeProvider);     // 確認可以正常獲得抽象型別例項

        Client client = new Client();

        client.TimeProvider = timeProvider; // 通過Setter實現注入

    }

}

1.5.5 介面注入

介面注入是將包括抽象型別注入的入口以方法的形式定義在一個接口裡,如果客戶型別需要實現這個注入過程,則實現這個介面,客戶型別自己考慮如何把抽象型別“引入”內部。實際上介面注入有很強的侵入性,除了要求客戶型別增加需要的Setter或Constructor注入的程式碼外,還顯式地定義了一個新的介面並要求客戶型別實現它。除非還有更外層容器使用的要求,或者有完善的配置系統,可以通過反射動態實現介面方式注入,否則筆者並不建議採用介面注入方式。

既然Martin Fowler文中提到了這個實現方式,就給出如下示例:

C#

/// 定義需要注入ITimeProvider的型別

interface IObjectWithTimeProvider

{

    ITimeProvider TimeProvider { get;set;}

}

/// 通過介面方式注入

class Client : IObjectWithTimeProvider

{

    private ITimeProvider timeProvider;

    /// IObjectWithTimeProvider Members

    public ITimeProvider TimeProvider

    {

        get { return this.timeProvider; }

         set { this.timeProvider = value; }

    }

}

Unit Test

[TestClass]

public class TestClient

{

    [TestMethod]

    public void Test()

    {

        ITimeProvider timeProvider = (new 
              Assembler()).Create();

        Assert.IsNotNull(timeProvider);     // 確認可以正常獲得抽象型別例項

        IObjectWithTimeProvider objectWithTimeProvider = new Client();

        objectWithTimeProvider.TimeProvider = timeProvider; // 通過介面方式注入

    }

}

1.5.6 基於Attribute實現注入——Attributer

如果做個歸納,Martin Fowler之前所介紹的三種模式都是在物件部分進行擴充套件的,隨著語言的發展(.NET從1.0開始,Java從5開始),很多在類元資料層次擴充套件的機制相繼出現,比如C#可以通過Attribute將附加的內容注入到物件上。直觀上的客戶物件有可能在使用上做出讓步以適應這種變化,但這違背了依賴注入的初衷,三個角色(客戶物件、Assembler、抽象型別)之中兩個不能變,那隻好在Assembler上下功夫,誰叫它的職責就是負責組裝呢?

為了實現上的簡潔,上面三個經典實現方式實際將抽象物件注入到客戶型別都是在客戶程式中(也就是那三個Unit Test部分)完成的,其實同樣可以把這個工作交給Assembler完成;而對於Attribute方式注入,最簡單的方式則是直接把實現了抽象型別的Attribute定義在客戶型別上,例如:

C#

(錯誤的實現情況)

    class SystemTimeAttribute : Attribute, ITimeProvider { … }

    [SystemTime]

    class Client { … }

相信您也發現了,這樣雖然把客戶型別需要的ITimeProvider通過“貼標籤”的方式告訴它了,但事實上又把客戶程式與SystemTimeAttribute“綁”上了,它們緊密地耦合在一起。參考上面的三個實現,當抽象型別與客戶物件耦合的時候我們引入了Assembler,當Attribute方式出現類似的情況時,我們寫個AttributeAssembler不就行了麼?還不行。設計上要把Attribute設計成一個“通道”,考慮到擴充套件和通用性,它本身要協助AttributeAssembler完成ITimeProvider的裝配,最好還可以同時裝載其他抽象型別來修飾客戶型別。示例程式碼如下:

C#

[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]

sealed class DecoratorAttribute : Attribute

{

    /// 實現客戶型別實際需要的抽象型別的實體型別例項,即得注入客戶型別的內容

    public readonly object Injector;

    private Type type;

    public DecoratorAttribute(Type type)

    {

        if (type == null) throw new ArgumentNullException("type");

        this.type = type;

        Injector = (new Assembler()).Create(this.type);

    }

    /// 客戶型別需要的抽象物件型別

    public Type Type { get { return this.type; } }

}

/// 幫助客戶型別和客戶程式獲取其Attribute定義中需要的抽象型別例項的工具類

static class AttributeHelper

{

    public static T Injector(object target)

        where T : class

    {

        if (target == null) throw new ArgumentNullException("target");

        Type targetType = target.GetType();

        object[] attributes = targetType.GetCustomAttributes(
              typeof(DecoratorAttribute), false);

        if ((attributes == null) || (attributes.Length <= 0)) return null ;

        foreach (DecoratorAttribute attribute in 
           (DecoratorAttribute[])attributes)

            if (attribute.Type == typeof(T))

                return (T)attribute.Injector;

        return null;

    }

}

[Decorator(typeof(ITimeProvider))]

// 應用Attribute,定義需要將ITimeProvider通過它注入

class Client

{

    public int GetYear()

    {

        // 與其他方式注入不同的是,這裡使用的ITimeProvider來自自己的Attribute

        ITimeProvider provider = 
             AttributeHelper.Injector(this);

        return provider.CurrentDate.Year;

    }

}

Unit Test

    [TestMethod]

    public void Test()

    {

        Client client = new Client();

        Assert.IsTrue(client.GetYear() > 0);

    }

1.5.7 小結

依賴注入雖然被Martin Fowler稱為一個模式,但平時使用中,它更多地作為一項實現技巧出現,開發中很多時候需要藉助這項技巧把各個設計模式所加工的成果傳遞給客戶程式。各種實現方式雖然最終目標一致,但在使用特性上有很多區別。

         Constructor方式:它的注入是一次性的,當客戶型別構造的時候就確定了。它很適合那種生命期不長的物件,比如在其存續期間不需要重新適配的物件。此外,相對Setter方式而言,在實現上Constructor可以節省很多程式碼;

         Setter方式:一個很靈活的實現方式,對於生命期較長的客戶物件而言,可以在執行過程中隨時適配;

         介面方式:作為注入方式具有侵入性,很大程度上它適於需要同時約束一批客戶型別的情況;

         屬性方式:隨著開發語言的發展引入的新方式,它本身具有範圍較小的固定內容侵入性(一個DecoratorAttribute),它也很適合需要同時約束一批客戶型別情景。它本身實現相對複雜一些,但客戶型別使用的時候非常方便——“打標籤”即可。

1.5 實現依賴注入1.5.1 背景介紹

設計模式中,尤其是結構型模式很多時候解決的就是物件間的依賴關係,變依賴具體為依賴抽象。平時開發中如果發現客戶程式依賴某個(或某類)物件,我們常常會對它們進行一次抽象,形成抽象的抽象類、介面,這樣客戶程式就可以擺脫所依賴的具體型別。

這個過程中有個環節被忽略了——誰來選擇客戶程式需要的滿足抽象型別的具體型別呢?通過後面的介紹你會發現很多時候建立型模式可以比較優雅地解決這個問題。但另一問題出現了,如果您設計的不是具體業務邏輯,而是公共庫或框架程式,這時候您是一個“服務方”,不是您呼叫那些構造型別,而是它們把抽象型別傳給您,怎麼鬆散地把加工好的抽象型別傳遞給客戶程式就是另一回事了。

    這個情形也就是常說的“控制反轉”,IOC:Inverse of Control;框架程式與抽象型別的呼叫關係就像常說的好萊塢規則:Don’t call me, I’ll call you.

參考Martin Fowler在《Inversion of Control Containers and the Dependency Injection pattern》一文,我們可以採用“依賴注入”的方式將加工好的抽象型別例項“注入”到客戶程式中,本書的示例也將大量採用這種方式將各種依賴項“注入”到模式實現的外部——客戶程式。下面我們結合一個具體的示例看看為什麼需要依賴注入,以及Martin Fowler文中提到的三種經典方式,然後依據C#語言的特質,再擴展出一個基於Attribter方式注入(參考原有的Setter命名,這裡將基於Attribute的方法稱為Attributer)。

1.5.2 示例情景

客戶程式需要一個提供System.DateTime型別當前系統時間的物件,然後根據需要僅僅把其中的年份部分提取出來,因此最初的實現程式碼如下:

C#

using System;

using System.Diagnostics;

namespace MarvellousWorks.PracticalPattern.Concept.DependencyInjection.Example1

{

    class TimeProvider

    {

相關推薦

C#依賴注入例項解析

1.5 實現依賴注入1.5.1 背景介紹 設計模式中,尤其是結構型模式很多時候解決的就是物件間的依賴關係,變依賴具體為依賴抽象。平時開發中如果發現客戶程式依賴某個(或某類)物件,我們常常會對它們進行一次抽象,形成抽象的抽象類、介面,這樣客戶程式就

C# 依賴注入中的 控制反轉(Assembly)實現

       對於剛接觸依賴注入的人來說,什麼面向切面,反射,依賴注入等等一時不好理解,  首先,都在說控制反轉,既然有反轉那麼就會有控制正轉,相信很多網上尋找控制反轉資料中,很少有提到過正轉。 正轉也簡單,就是我們通常呼叫的

【Spring原始碼閱讀】populateBean實現 依賴注入原始碼解析

在完成Bean例項化後,Spring容器會給這個Bean注入相關的依賴Bean,在原始碼中,這一步通過類AbstractAutowireCapableBeanFactory中的populateBean方法完成。 測試程式碼 下面開始進入原始碼分析之前,先基於以下例項進行: /

C#依賴注入控制反轉IOC實現詳解

原文: C#依賴注入控制反轉IOC實現詳解 IOC的基本概念是:不建立物件,但是描述建立它們的方式。在程式碼中不直接與物件和服務連線,但在配置檔案中描述哪一個元件需要哪一項服務。容器負責將這些聯絡在一起。 舉個例子,元件A中有類ClassA,元件B中有介面IB和其對應的實現類B1和B2。 那麼,現在Cl

Spring 筆記 -04- 依賴注入例項 - 實現印表機功能(maven)

Spring 筆記 -04- 依賴注入例項 - 實現印表機功能(maven) 前面的文章是基礎,如果是剛開始學習,最好依次檢視:Spring 筆記 你可能會感覺這是把一個簡單的問題弄複雜了, 不要慌,你的錯覺沒有錯, 這裡主要是學習 Spring 的依賴注入, 很重要! 問題

Spring MVC中,基於XML配置和基於註解的依賴注入例項

一、首先是基於XML配置的依賴注入例項   在本例項中,Spring MVC並非主要講解內容,其檔案正規化不再重複,而有關依賴注入檔案包括:介面類car.java,實現了car介面的Taxi,java和Train.java。在User類中,有一個Car物件屬性。即此Car即

C#依賴注入那些事兒(一)

1 IGame遊戲公司的故事 1.1 討論會 話說有一個叫IGame的遊戲公司,正在開發一款ARPG遊戲(動作&角色扮演類遊戲,如魔獸世界、夢幻西遊這一類的遊戲)。一般這類遊戲都有一個基本的功能,就是打怪(玩家攻擊怪物,藉此獲得經驗、虛擬貨幣和虛擬裝備

C#依賴注入

依賴注入的另外一個意思便是控制反轉,也就是說將控制權交給被呼叫者。 依賴注入的用處便是減小模組化設計的耦合性,即增加可維護性。 假設我們有A,B兩個模組,B要呼叫A來實現某種功能,通常最直接的做法是將A在B中建立例項: ps: interface IA{ voi

C# 依賴注入

依賴注入 1. 什麼是依賴注入    我們建立一個SkiCardController需要應用程式中的一些其他服務才能處理檢視,建立和編輯的請求。具體來說,他用SkiCardContext訪問資料,用UserManager訪問當前使用者的資訊,用IAuthorizationService檢查當前使用者是否有許可

在net Core3.1上基於winform實現依賴注入例項

目錄 在net Core3.1上基於winform實現依賴注入例項 1.背景 2.依賴注入 2.1依賴注入是什麼? 2.1依賴注入的目的 2.2依賴注入帶來的好處

c++ coredump例項解析

一、背景     經過重重除錯後,看到編譯成功的那一刻,內心充滿歡喜。當程式一執行,卻經常出現coredump的情況,此時內心是崩潰的。我想程式設計師經常會碰到這種情況,尤其使用c++語言編寫程式碼,由於沒有自動記憶體管理,經常會出現coredump情況,主要原因有以下幾

.NET Core實戰專案之CMS 第三章 入門篇-原始碼解析配置檔案及依賴注入

作者:依樂祝 原文連結:https://www.cnblogs.com/yilezhu/p/9998021.html 寫在前面 上篇文章我給大家講解了ASP.NET Core的概念及為什麼使用它,接著帶著你一步一步的配置了.NET Core的開發環境並建立了一個ASP.NET Core的mvc專

Spring 控制反轉(IOC) | 依賴注入(DI)的解析

學習過Spring框架的人一定都會聽過Spring的IoC(控制反轉) 、DI(依賴注入)這兩個概念,對於初學Spring的人來說,總覺得IoC 、DI這兩個概念是模糊不清的,是很難理解的,今天和大家分享網上的一些技術大牛們對Spring框架的IOC的理解以及談談我對Spri

AutowiredAnnotationBeanPostProcessor依賴注入解析

AutowiredAnnotationBeanPostProcessor依賴注入解析 Spring的bean建立流程 控制反轉和依賴注入 AutowiredAnnotationBeanPostProcessor簡介 A

SpringMVC學習指南【筆記1】建立bean例項的方法和依賴注入

Spring MVC 主要從Spring框架、Servlet、JSP這3個方面來講。   Java企業版技術包括JMS、EJB、JSF、JPA。   Java企業版容器:GlassFish、JBoss、Oracle、Weblogic、IBM WebSphere   T

C++類和物件例項解析

C++既是面向物件也是面向過程的語言,在這裡就有一個重要的概念——類。         何謂類?類是對物件的一種抽象,舉例來講:每一個實實在在存在的人就是一個物件,人有很多共同的特徵(一個頭,兩條腿,能走,能跑),這具有共同特徵的人就成為一個類。類是一個抽象的名詞,每一個人(即物件)是這個類的例項。   

六、基於xml的bean例項化和依賴注入

例項化bean bean 定義基本上就是用來建立一個或多個物件的配置,當需要一個 bean 的時候,容器檢視配置並且根據 bean 定義封裝的配置元資料建立(或獲取)一個實際的物件。 如果您想要配置的一個bean定義static巢狀類,你必須使用二進位

yii2.0依賴注入DI程式碼例項

場景 使用者評論成功後可以靈活配置選用GMAIL、qq或其他郵箱傳送郵件。 檔案功能 EmailSenderInterface.php 郵件服務定義介面 GmailSender.php gmail類實現EmailSenderInterface QqSend

tp5依賴注入(自動例項化):解決了像類中的方法傳物件的問題

app\index\Demo1.php namespace app\index\controller; /* 容器與依賴注入的原理 ----------------------------- 1.任何的url訪問,最終都是定位到控制器,由控制器中某換個具體方法去執行 2.一個控制器對應著一個類,如果這些

spring 框架中的依賴注入(IOC--設值注入)---使用xml簡單配置檔案---的具體例項的簡單實現

體現了具體專案工程裡面的分層,dao,daoImpl,service,serviceImpl,action。讓你真正的理解這為啥分層。 畢竟當年我剛剛畢業的時候,再找工作我就不是很清楚為什麼有這麼幾層