動手造輪子:實現一個簡單的依賴註入(零)

来源:https://www.cnblogs.com/weihanli/archive/2019/10/27/implement-dependency-injection.html
-Advertisement-
Play Games

動手造輪子:實現一個簡單的依賴註入(零) Intro 依賴註入為我們寫程式帶來了諸多好處,在微軟的 .net core 出來的同時也發佈了微軟開發的依賴註入框架 "Microsoft.Extensions.DependencyInjection" ,大改傳統 asp.net 的開發模式,asp.ne ...


動手造輪子:實現一個簡單的依賴註入(零)

Intro

依賴註入為我們寫程式帶來了諸多好處,在微軟的 .net core 出來的同時也發佈了微軟開發的依賴註入框架 Microsoft.Extensions.DependencyInjection,大改傳統 asp.net 的開發模式,asp.net core 的開發更加現代化,更加靈活,更加優美。

依賴註入介紹

要介紹依賴註入,首先來聊一下控制反轉(IoC)

Ioc—Inversion of Control,即“控制反轉”,不是什麼技術,而是一種設計思想。Ioc意味著將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。

  • 誰控制誰,控制什麼:傳統程式設計,我們直接在對象內部通過 new 進行創建對象,是程式主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由 IoC 容器來控制對 象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什麼?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
  • 為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程式是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙創建及註入依賴對象;為何是反轉?因為由容器幫我們查找及註入依賴對象,對象只是被動的接受依賴對象,所以是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。

IoC 對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程式原本是老大,要獲取什麼資源都是主動出擊,但是在 IoC/DI 思想中,應用程式就變成被動的了,被動的等待 IoC 容器來創建並註入它所需要的資源了。

IoC 很好的體現了面向對象設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由 IoC 容器幫對象找相應的依賴對象並註入,而不是由對象主動去找。

DI—Dependency Injection,即“依賴註入”組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係註入到組件之中依賴註入的目的並非為軟體系統帶來更多功能,而是為了提升組件重用的頻率,併為系統搭建一個靈活、可擴展的平臺。通過依賴註入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。

理解DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰註入誰,註入了什麼”,那我們來深入分析一下:

  ●誰依賴於誰:當然是應用程式依賴於 IoC 容器

  ●為什麼需要依賴:應用程式需要 IoC 容器來提供對象需要的外部資源

  ●誰註入誰:很明顯是 IoC 容器註入應用程式里依賴的對象

  ●註入了什麼:就是註入某個對象所需要的外部資源/依賴

 

依賴註入明確描述了 “被註入對象依賴 IoC 容器配置依賴對象”,依賴註入是控制反轉設計思想的一種實現。

依賴註入的好處:

  • 對象的創建和銷毀完全交給 ioc 容器去做,不再需要在應用中關心對象的創建的和銷毀,這對於 C# 里的 IDisposable 對象來說尤為重要,自己去 new 的時候,對於一些新手來說可能會忘記使用 using 或手動 dispose
  • 對象的復用,有時候很多對象沒有必要每次用的時候就去創建一次,使用 ioc 可以控制在同一生命周期內的對象只被創建一次
  • 依賴關係更清晰
  • 更好的實現面向介面編程,替換實現只需要註入服務的時候換成另外一種實現就可以了

大概設計

大體使用類似於微軟的依賴註入框架,但是比微軟的依賴註入框架簡單一些,性能也有待優化。

  • 服務生命周期:服務的生命周期沿用微軟的服務生命周期,分為 Singleton/Scoped/Transient,預設值是 Singleton 單例模式
  • 服務註冊方式:支持所有微軟依賴註入的註冊方式,實例註入/類型註入/介面-實現註入/func 註入
  • 註入方式:目前僅支持依賴註入,構造方法註入,未來暫時也沒有支持屬性註入的打算(支持的話也不複雜,但是依賴關係就不清晰了,也不推薦用),構造方法註入支持直接註入 IEnumerable<T>IReadOnlyCollection<T>IReadOnlyList<T> 來支持獲取一個介面多個實現的註入,支持泛型註入

DI 相關類圖:

體驗一下

可以參考單元測試:

using(IServiceConatiner container = new ServiceContainer())
{
    container.AddSingleton<IConfiguration>(new ConfigurationBuilder()
         .AddJsonFile("appsettings.json")
         .Build()
    );
    container.AddScoped<IFly, MonkeyKing>();
    container.AddScoped<IFly, Superman>();

    container.AddScoped<HasDependencyTest>();
    container.AddScoped<HasDependencyTest1>();
    container.AddScoped<HasDependencyTest2>();
    container.AddScoped<HasDependencyTest3>();
    container.AddScoped(typeof(HasDependencyTest4<>));

    container.AddTransient<WuKong>();
    container.AddScoped<WuJing>(serviceProvider => new WuJing());
    container.AddSingleton(typeof(GenericServiceTest<>));

    var rootConfig = container.ResolveService<IConfiguration>();

    Assert.Throws<InvalidOperationException>(() => container.ResolveService<IFly>());
    Assert.Throws<InvalidOperationException>(() => container.ResolveRequiredService<IDependencyResolver>());

    using (var scope = container.CreateScope())
    {
        var config = scope.ResolveService<IConfiguration>();

        Assert.Equal(rootConfig, config);

        var fly1 = scope.ResolveRequiredService<IFly>();
        var fly2 = scope.ResolveRequiredService<IFly>();
        Assert.Equal(fly1, fly2);

        var wukong1 = scope.ResolveRequiredService<WuKong>();
        var wukong2 = scope.ResolveRequiredService<WuKong>();

        Assert.NotEqual(wukong1, wukong2);

        var wuJing1 = scope.ResolveRequiredService<WuJing>();
        var wuJing2 = scope.ResolveRequiredService<WuJing>();

        Assert.Equal(wuJing1, wuJing2);

        var s0 = scope.ResolveRequiredService<HasDependencyTest>();
        s0.Test();
        Assert.Equal(s0._fly, fly1);

        var s1 = scope.ResolveRequiredService<HasDependencyTest1>();
        s1.Test();

        var s2 = scope.ResolveRequiredService<HasDependencyTest2>();
        s2.Test();

        var s3 = scope.ResolveRequiredService<HasDependencyTest3>();
        s3.Test();

        var s4 = scope.ResolveRequiredService<HasDependencyTest4<string>>();
        s4.Test();

        using (var innerScope = scope.CreateScope())
        {
            var config2 = innerScope.ResolveRequiredService<IConfiguration>();
            Assert.True(rootConfig == config2);

            var fly3 = innerScope.ResolveRequiredService<IFly>();
            fly3.Fly();

            Assert.NotEqual(fly1, fly3);
        }

        var flySvcs = scope.ResolveServices<IFly>();
        foreach (var f in flySvcs)
            f.Fly();
    }

    var genericService1 = container.ResolveRequiredService<GenericServiceTest<int>>();
    genericService1.Test();

    var genericService2 = container.ResolveRequiredService<GenericServiceTest<string>>();
    genericService2.Test();
}

更多詳情可以參考:< https://github.com/WeihanLi/WeihanLi.Common/blob/dev/test/WeihanLi.Common.Test/DependencyInjectionTest.cs >

More

源碼已經在 Github 上,可以自行下載閱覽或等後面的幾篇文章分享解讀

Reference


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

-Advertisement-
Play Games
更多相關文章
  • Python 提供了兩個基本的 socket 模塊。 第一個是 Socket,它提供了標準的 BSD Sockets API。 第二個是 SocketServer, 它提供了伺服器中心類,可以簡化網路伺服器的開發。 1、Scoket類型 套接字格式: socket(family,type[,prot ...
  • 單點登陸說明:在多個應用系統中,只需要登錄一次,就可以訪問其他相互信任的應用系統。 單點註銷說明:在多個應用系統中,只需要註銷一次,就可以註銷其他相互信任的應用系統的用戶登陸狀態。 下圖是標準單點登陸流程圖: 單點登陸與單點註銷具體實現: 1. 一共有三個相互獨立的項目,cas-server;sso ...
  • 眾所周知,線段樹是algo中很重要的一項! 一.簡介 線段樹是一種二叉搜索樹,與區間樹相似,它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。 使用線段樹可以快速的查找某一個節點在若幹條線段中出現的次數,時間複雜度為O(logN)。而未優化的空間複雜度為2N,實際應用時一般還要開 ...
  • (手機橫屏看源碼更方便) 註:java源碼分析部分如無特殊說明均基於 java8 版本。 註:線程池源碼部分如無特殊說明均指ThreadPoolExecutor類。 簡介 上一章我們一起重溫了下線程的生命周期(六種狀態還記得不?),但是你知不知道其實線程池也是有生命周期的呢?! 問題 (1)線程池的 ...
  • JPA是Spring Data框架的其中一個模塊,全稱為Java Persistence API,是一個持久層規範,Hibernate框架是JPA實現之一。 本文內容: (1)項目構建 (2)數據訪問層與業務層 (3)自定義數據存儲邏輯 (4)方法名查詢 (5)使用@Query註解 ...
  • [TOC] 軟體開發架構 C/S架構 client:客戶端 server:服務端 優點:軟體的使用穩定,網路資源占用少 缺點: 若需要使用多個軟體,需要下載多個客戶端 服務端更新後,用戶也需要跟著下載更新 B/S架構 browser:瀏覽器 server:服務端 優點:以瀏覽器充當客戶端,服務端更新 ...
  • 什麼是 PWM 在解釋 PWM 之前首先來瞭解一下電路中信號的概念,其中包括模擬信號和數字信號。 模擬信號 是一種連續的信號,與連續函數類似,在圖形上表現為一條不間斷的連續曲線。 數字信號 為只能取有限個數值的信號,比如電腦中的高電平(1)和低電平(0)。 PWM(Pulse Width Modu ...
  • 1.基本概念 多線程與非同步是兩個不同概念,之所以把這兩個放在一起學習,是因為這兩者雖然有區別,但也有一定聯繫。 多線程是一個技術概念,相對於單線程而言,多線程是多個單線程同時處理邏輯。例如,假如說一個人把水從A地提到B點可看作是單線程,那麼如果兩個人同時去做事(可以是相同的一件事,也可以是不同的一件 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...