1. 程式人生 > >動手造輪子:實現一個簡單的依賴注入(零)

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

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

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

  • https://blog.csdn.net/sinat_21843047/article/details/80297951
  • https://www.cnblogs.com/artech/p/inside-asp-net-core-03-04.html
  • https://github.com/aspnet/DependencyInjection/tree/rel/2.0.0
  • https://github.com/microsoft/MinIoC
  • https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/DependencyInjection