.net 溫故知新:【7】IOC控制反轉,DI依賴注入

IOC控制反轉

大部分應用程序都是這樣編寫的:編譯時依賴關係順着運行時執行的方向流動,從而生成一個直接依賴項關係圖。 也就是說,如果類 A 調用類 B 的方法,類 B 調用 C 類的方法,則在編譯時,類 A 將取決於類 B,而 B 類又取決於類 C

image

應用程序中的依賴關係方向應該是抽象的方向,而不是實現詳細信息的方向。
而這就是控制反轉的思想。
應用依賴關係反轉原則後,A 可以調用 B 實現的抽象上的方法,讓 A 可以在運行時調用 B,而 B 又在編譯時依賴於 A 控制的接口(因此,典型的編譯時依賴項發生反轉)。 運行時,程序執行的流程保持不變,但接口引入意味着可以輕鬆插入這些接口的不同實現。

image

上下不同的實現方式在於之前的依賴關係是A->B->C,控制反轉後A->B接口->C接口,然後具體的B,C實現又是B->B接口的反轉依賴。這樣的好處就是A只依賴B接口而不是依賴實現,具體我們要實現是什麼只需要按照業務需求進行編寫,並且可以隨時替換實現而不會影響A的實現,這種思想就是控制反轉。

如下是順序依賴:

        public class A
        {
            //依賴具體類
            public B b;
            public C c;
            public A(B _b, C _c) {
                b = _b;
                c = _c;
            }
            public void Listen()
            {
                b.SayHi();
                c.SayBye();
            }
        }

        public class B
        {
            public void SayHi()
            {
                Console.WriteLine("hi...");
            }
        }
        public class C
        {
            public void SayBye()
            {
                Console.WriteLine("bye...");
            }
        }

如下是控制反轉:

        public class A
        {
            //依賴接口
            public IB b;
            public IC c;
            public A(IB _b, IC _c)
            {
                b = _b;
                c = _c;
            }
            public void Listen()
            {
                b.SayHi();
                c.SayBye();
            }
        }

        public interface IB
        {
            public void SayHi();
        }
        public interface IC
        {
            public void SayBye();
        }

DI依賴注入

.NET 支持依賴關係注入 (DI) 軟件設計模式,這是一種在類及其依賴項之間實現控制反轉 (IoC) 的技術。
我們首先用代碼來看什麼是DI,在.net提供的擴展包Microsoft.Extensions.DependencyInjection中來完成DI,nuget安裝。

image

然後我們實現接口B和接口C,實現我們可以說英語,也可以說漢語,我們在SayHi和SayBye中輸出漢語。

        public class B : IB
        {
            public void SayHi()
            {
                Console.WriteLine("你好...");
            }
        }

        public class C : IC
        {
            public void SayBye()
            {
                Console.WriteLine("再見...");
            }
        }

然後在服務容器中註冊依賴關係。 .NET 提供了一個內置的服務容器 IServiceProvider。 服務通常在應用啓動時註冊,並追加到 IServiceCollection。 添加所有服務後,可以使用 BuildServiceProvider 創建服務容器,然後在容器中直接取“要”對象而不用去管它如何實例化,並且DI具備傳染性,假如B引用了D接口ID,那麼我們註冊B並在獲取B實例時,引用的D也會實例化。

            //IServiceCollection 服務
            IServiceCollection services = new ServiceCollection();
            //服務註冊
            services.AddTransient<A>();
            services.AddTransient<IB, B>();
            services.AddTransient<IC, C>();
            //創建服務容器
            var serviceProvider = services.BuildServiceProvider();
            //獲取服務
            var a = serviceProvider.GetRequiredService<A>();
            //使用
            a.Listen();
            Console.ReadKey();

image

這就是通過DI依賴注入的方式來實現IOC的思想,或許你會好奇爲什麼我們不直接實例化A,然後在構造方法裏面傳進去就行了,也就不依賴DI實現了。但是如果程序結構更復雜些呢,比如我上面提到的B又有D,D又有F呢,這樣我們在構造的時候不是一直要new很多對象,而且同一個接口的不同實現我們還要去找實例化處的代碼進行修改。比如我SayHI我想說英文呢?那麼我們假設實現一個BB,然後在服務註冊的地方註冊BB就可以了。

        public class BB : IB
        {
            public void SayHi()
            {
                Console.WriteLine("hello...");
            }
        }

然後註冊BB services.AddTransient<IB, BB>(),而不用去改任何邏輯。
image

服務生命週期

在註冊服務的時候我使用的AddTransient方法,表示註冊的服務是瞬態的,也就是每次請求都是重新創建實例。同時還提供其它註冊服務的方法。
image
服務有三種聲明週期:

  • 瞬態
  • 作用域
  • 單例
  1. 瞬態
    服務是每次從服務容器進行請求時創建的。 這種生存期適合輕量級、 無狀態的服務。 用 AddTransient 註冊服務。在處理請求的應用中,在請求結束時會釋放暫時服務。

  2. 作用域
    指定了作用域的生存期指明瞭每個客戶端請求(連接)創建一次服務。 向 AddScoped 註冊範圍內服務。在處理請求的應用中,在請求結束時會釋放有作用域的服務。
    想asp.net 在處理一個請求的時候是一個作用域,同樣我們自己也可以定義作用域。使用serviceProvider.CreateScope()創建作用域,在作用域釋放後對象將被釋放。
    image
    我們使用AddScoped添加對象,然後在作用域中取兩個A對象進行比較,可以看到是True
    如果我們用AddTransient註冊A,即使在作用域內兩個對象比較也是不一樣的,結果爲False
    image

  3. 單例
    單例大家應該好理解,就是設計模式中的單例,使用AddSingleton 註冊,在首次請求它們時進行創建;或者在向容器直接提供實現實例時由開發人員進行創建。 很少用到此方法,因爲可能是線程不安全的,如果服務中有狀態。

其它

在Microsoft.Extensions.DependencyInjection中只能用構造函數注入,其它框架還提供屬性注入,比如autofac。至於原因不得而知,當然也看個人喜好。查了些資料說是構造函數注入更科學,在對象創建的瞬間對象的構造方法將服務實例化,避免邏輯問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章