使用Microsoft的IoC框架:Unity來對.NET應用進行解耦

来源:http://www.cnblogs.com/frankyou/archive/2016/12/08/6143966.html
-Advertisement-
Play Games

1、IoC/DI簡介 IoC 即 Inversion of Control,DI 即 Dependency Injection,前一個中文含義為控制反轉,後一個譯為依賴註入,可以理解成一種編程模式,詳細的說明可參見大牛Martin Fowler的強文 http://martinfowler.com/ ...


1、IoC/DI簡介

IoC 即 Inversion of Control,DI 即 Dependency Injection,前一個中文含義為控制反轉,後一個譯為依賴註入,可以理解成一種編程模式,詳細的說明可參見大牛Martin Fowler的強文 http://martinfowler.com/articles/injection.html,借用Hollywood的名言:Don't call us, we'll call you,意即你呆著別動,到時我會找你。控制反轉的核心是控制權的轉移,從原有的應用程式轉移到框架如IoC容器,從而實現模塊間的解耦。

2、Unity是什麼?

Unity是微軟Patterns & Practices團隊所開發的一個輕量級的,並且可擴展的依賴註入(Dependency Injection)容器,它支持常用的三種依賴註入方式:構造器註入(Constructor Injection)、屬性註入(Property Injection),以及方法調用註入(Method Call Injection)。現在Unity最新的版本的3.5版,可以在微軟的開源站點:https://github.com/unitycontainer/unity下載最新的發佈版本和文檔。

它有助於構建松耦合的應用程式和為開發者提供以下便利:

  • 簡化對象的創建,特別在分層對象結構和依賴的情形下;
  • 它支持需求的抽象化,這允許開發人員在運行時或在配置文件中指定依賴,簡化橫切關註點(crosscutting concerns)的管理;
  • 它通過把組件配置推給容器來決定,增加了靈活性;
  • 服務定位能力; 這使客戶端能夠存儲或緩存容器;
  • 輕鬆構建松耦合結構的程式,從而讓整個程式框架變得清晰和易於維護。

3、如何使用Unity?

下麵我們用一個簡單的例子來演示如何使用Ioc框架:Unity。我們的大多數應用程式都是由兩個或是更多的類通過彼此的合作來實現業務邏輯,這使得每個對象都需要獲取與其合作的對象(也就是它所依賴的對象)的引用。如果這個獲取過程要靠自身實現,那麼這將導致代碼高度耦合併且難以維護和調試。使用Unity就是要解決這個這個問題。

Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中將具體依賴註入劃分為三種形式,即構造器註入、屬性(設置)註入和介面註入,習慣將其劃分為一種(類型)匹配和三種註入:

  • 類型匹配(Type Matching):雖然我們通過介面(或者抽象類)來進行服務調用,但是服務本身還是實現在某個具體的服務類型中,這就需要某個類型註冊機制來解決服務介面和服務實現類型之間的匹配關係
  • 構造器註入(Constructor Injection):IoC容器會智能選擇選擇和調用適合的構造函數以創建依賴的對象。如果被選擇的構造函數具有相應的參數,IoC容器在調用構造函數之前解析註冊的依賴關係並自行獲得相應參數對象;
  • 屬性註入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創建之後,IoC容器會自動初始化該屬性
  • 方法註入(Method Injection):如果被依賴對象需要調用某個方法進行相應的初始化,在該對象創建之後,IoC容器會自動調用該方法。

依賴翻轉的核心原則:

1、高層模塊不應該依賴底層模塊,兩個都應該依賴抽象(抽象類或介面)

2、抽象不應該依賴細節,細節應該依賴抽象。

下麵我採用“屬性註入(Property Injection)”的方式演示如何使用Unity框架。

3.1、定義介面類

一個簡單日誌記錄介面類,用於記錄請求的報文。

    public interface ILogger
    {
        Tuple<string, string> GetLogContent(AtomRequest request, AtomResponse response = null);
        void Log(Tuple<string, string> logInfo);
    }

3.2、定義介面的實現類

定義2個介面的實現類,分別用來實現對請求的響應的報文進行日誌記錄。

internal class LogRequest : ILogger
{
    public Tuple<string, string> GetLogContent(AtomRequest request, AtomResponse response = null)
    {
        var reqType = request.GetType().Name;
        switch (reqType)
        {
            case "AtomQueryRequest":
            {
                var req = (AtomQueryRequest) request;
                return Tuple.Create(request.CollectionId, string.Format("流水號:{0} 交易類型:{1}", req.TransNo, req.TransType));
            }
            case "AtomSaleRequest":
            {
                var req = (AtomSaleRequest) request;
                return Tuple.Create(req.CollectionId, string.Format("流水號:{0} 訂單號:{1} 金額:{2}", req.TransNo, req.OrderNo, req.LocalAmount));
            }
            default:
            {
                throw new CheckRequestException("無效的交易類型:".Contact(reqType));
            }
        }
    }

    public void Log(Tuple<string, string> logInfo)
    {
        LogManager.InfoRequest(logInfo.Item1, logInfo.Item2);
    }
}

3.3、定義容器並註冊介面及介面實現類之間的映射關係

項目引用:Microsoft.Practices.Unity.dll

internal class ServiceContainer
{
    // 核心容器類型的定義,設置為靜態的,公開的,可為其它任何類型調用。
    public static UnityContainer RtpContainer;

    // 初始化容器併在容器中註冊項目程式中所有的依賴關係
    static ServiceContainer()
    {
        RtpContainer = new UnityContainer();

        // 每次調用,容器都會生成一個新的對象實例
        RtpContainer.RegisterType<IResponseProcessor, ResponseProcessor>();

        // 註冊為單例,任何時候調用都使用同一個對象實例
        RtpContainer.RegisterType<ILogger, LogRequest>("Request", new ContainerControlledLifetimeManager());
        RtpContainer.RegisterType<ILogger, LogResponse>("Response", new ContainerControlledLifetimeManager());
    }
}

 什麼時候註冊為單例,我的個人標準為:實現類,比如:LogRequest類,沒有靜態成員或者靜態成員沒有併發寫的可能都可以用,使用單例可以減少頻繁創建對象可能造成的開銷。在註冊類型時,如果要註冊為單例模式,額外傳入一個:new ContainerControlledLifetimeManager() 參數即可,表示創建對象的生命周期由容器來控制。另外如果一個介面由多個實現類,如上面的LogRequest和LogResponse都實現了ILogger介面。這樣在註冊map映射關係時,需要額外使用一個name參數(比如上面的“Request”,“Response”)來唯一標識map關係。使用XML也可以實現依賴關係的註冊,但我更傾向於使用:約定優於配置(convention over configuration)的原則,也稱作按約定編程,是一種軟體設計範式,旨在減少軟體開發人員需做決定的數量,獲得簡單的好處,而又不失靈活性。

3.4、依賴註入

internal abstract class AbstractRequest
{
    static AbstractRequest()
    {
        JsConfig.EmitCamelCaseNames = true;
        JsConfig.IncludeNullValues = true;
    }

    [Dependency("Request")]
    public ILogger Logger { get; set; }

    [Dependency]
    public IRequestCheck RequestCheck { get; set; }

    protected virtual void CheckRequest(AtomRequest request)
    {
        if (null == request)
        {
            throw new ArgumentNullException("request", "請求實體不能為NULL");
        }
    }
}

上面的代碼我採用了“屬性註入(Property Injection)”的方式註入了一個Logger屬性,並且Dependency屬性類的name參數為:Request,標識當AbstractRequest的實現類被實例化時,IoC容器自動初始化該Logger屬性為一個LogRequest對象實例。

3.5、使用註入的屬性

internal class ProcessAtomSaleRequest : AbstractRequest, IAtomRequest
{
    [Dependency("AtomSale")]
    public MessageProviderFactory MessageProviderFactory { get; set; }

    public object Execute(AtomRequest request)
    {
        CheckRequest(request);
        
        // 使用AbstractRequest類中註入的屬性Logger,ProcessAtomSaleRequest被實例化時Logger屬性自動被初始化為LogRequest實例對象
        Logger.Log(Logger.GetLogContent(request));
    }
}

3.6、使用容器來解析並創建對象實例

public class AtomSale : AtomTransaction
{
    protected override object ProcessRequest(AtomRequest request)
    {
        return ((IAtomRequest)ServiceContainer.RtpContainer.Resolve(typeof(IAtomRequest), "AtomSale")).Execute(request);
    }
}

 ServiceContainer.RtpContainer就是我們前面定義的靜態的、公共的容器類(類型為:UnityContainer),在第一次被調用時初始化。Resolve方法通過指定抽象類型及對應的name屬性來確定唯一映射關係並創建對象,最後執行對象的Execute方法。

 

 總結:

使用IoC框架後,使用相同架構模型的應用,其高層抽象可以完全移植,只須關註實現類的業務細節即可,業務邏輯修改時也只須改動實現的部分,這樣就實現了抽象層和實現層的分離,同樣對象創建職責也做了轉移。系統解耦或松耦合也就順理成章了。如果所有代碼都揉合在一起,任何代碼的修改都可能對其它代碼造成影響,如果沒能細緻和全面的回歸測試,線上故障也難免發生。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 看了下Nhibernate的入門Demo,感覺測試驅動開發會更效率.當然,你可能覺得不是還要額外編程單元測試代碼嗎?開發怎麼會更效率? 一句話解釋之,磨刀不誤砍柴工. 那就開始入門吧 ~.~ 筆者使用的vs2013+Resharper 8.2. 1.使用Resharper比較方便,所以,首先 Res ...
  • 此配置節的作用就是往Web程式中添加URL的映射,從而達到用戶訪問映射後的URL(如/Page/AAA)也能訪問到源URL(如/Page/PageAAA.aspx)的效果。這也是URL映射本來的作用。 詳細配置如下 其中要啟用這個URL映射的必須要把enabled設置成true,add和remove ...
  • Smobiler是一個在VS環境中使用.Net語言來開發APP的開發平臺,也許比Xamarin更方便 ...
  • 前面已經介紹了新增/修改/刪除了, 接下來介紹一下Rainbow的Read方法. 一、Read -- Rainbow原生 1. 先看測試代碼 Rainbow在讀取數據這一塊, 就只提供了這幾個方法, 當然, Dapper的方法, 在這裡仍然是可以用的, 通過db.Query的方式就可以用了 2. 源 ...
  • A-PC端: 1-頁面--multiple是控制單張還是多張圖片上傳 2-後臺獲取圖片文件: 3-保存示例: B-APP: 前端頁面長什麼樣不管了,後臺拿到的是base64的字元串集合. 1-保存示例: C-跨域保存問題: 跨域的常見場景如下圖所示:我們通過電腦的網路影射,連接到所需要的目錄,這裡添 ...
  • 該文純粹屬於個人學習,有不足之處請多多指教! 效果圖: 單擊Detail下麵出現詳細,效果如下: 為了使操作時兩個不同的數據源相互干擾,使用局部視圖刷新,代碼如下: 首先介紹主頁Index代碼: 1 @model IEnumerable<Framework.Models.Customer> 2 @u ...
  • 前言 參數驗證是一個常見的問題,無論是前端還是後臺,都需對用戶輸入進行驗證,以此來保證系統數據的正確性。對於web來說,有些人可能理所當然的想在前端驗證就行了,但這樣是非常錯誤的做法,前端代碼對於用戶來說是透明的,稍微有點技術的人就可以繞過這個驗證,直接提交數據到後臺。無論是前端網頁提交的介面,還是 ...
  • 上一篇介紹了Rainbow的Create方法, 這裡就來介紹一下Update方法吧, 畢竟新增和修改是雙胞兄弟嘛. 一、Update 1. 測試代碼: 使用方法和Insert方法類似, Update方法由兩個參數, 第一個參數是Id, 第二個參數是dynamic data, 如果你不想更新全部的列, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...