控制反轉(IOC容器)-Autofac入門

張歐昊辰發表於2021-06-11

注意:本文為原創文章,任何形式的轉載、引用(包括但不限於以上形式)等,須先徵得作者同意,否則一切後果自負。

簡介

Autofac 是一個令人著迷的.NET IoC 容器。

它管理類之間的依賴關係。當應用程式的規模和複雜性隨著時間不斷增長時,也能易於更改。這是通過將常規 .NET 類視為元件來實現的

入門

將 Autofac 整合到我們的應用程式的基本模式是:

  • 時刻牢記用控制反轉(IOC)來構建我們的應用程式。
  • 新增Autofac引用。
  • 在應用程式啟動時…
  • 建立一個ContainerBuilder。
  • 註冊元件。
  • 構建容器並將其儲存以備後用。
  • 在應用程式執行期間…
  • 從容器建立一個生命週期範圍。
  • 使用生命週期範圍來解析(resolve)元件的例項。

構建應用程式

控制反轉背後的思想是:與其將應用程式中的類捆綁在一起,讓類“新建”它們的依賴關係,不如在類構造期間把依賴項傳遞進來,如果每次傳遞的依賴項不相同,則可以隨時切換依賴關係並呼叫相應的依賴項的實現。

對於我們的示例應用程式,我們將定義一個將當前時間寫出的類。但是,我們不希望將它繫結到Console,因為我們希望能夠稍後測試該類,或者在控制檯不可用的地方使用它。

我們將盡可能地把寫出時間的機制抽象化,因為如果我們以後想把它變成一個寫出明天日期的程式版本,那麼就可以快速實現該功能。

程式碼如下:

public interface IOutput
{
	void Write(string content);
}
public class Output : IOutput
{
	public void Write(string content)
	{
		Console.WriteLine(content);
	}
}
public interface ITodayWriter
{
	void WriteDate();
}
public class TodayWriter : ITodayWriter
{
	private IOutput _output;

	public TodayWriter(IOutput output)
	{
		_output = output;
	}

	public void WriteDate()
	{
		_output.Write(DateTime.Now.ToString());
	}
}

現在我們有了一個合理的結構化的依賴集,下面我們就把Autofac加入進來!

新增 Autofac 引用

第一步是將Autofac引用新增到我們的專案中。

對於我們的示例,僅使用核心Autofac包就夠了,因為該包包含了我們所要使用的全部核心功能。

引入Autofac最簡單的方法是通過NuGet。

應用啟動

在應用程式啟動時,我們需要建立一個ContainerBuilder並在其中註冊我們的元件。

元件可以是一個表示式、.NET型別或其他程式碼(該程式碼公開一個或多個服務,並可以接受其他依賴項)。

簡單來說,考慮一個實現介面的 .NET 型別,像這樣:

public class SomeType : IService
{
}

我們可以用以下兩種方法來解決這個問題:

  • 作為型別本身,SomeType
  • 作為介面,IService

在這種情況下,元件是SomeType,它公開的服務是SomeType和IService(這一點你可能不太明白,不過沒關係,後面我們將會講解)。

在Autofac中,您可以使用以下內容進行註冊ContainerBuilder:

// 建立一個構造器
var builder = new ContainerBuilder();

// 註冊元件,並公開它的服務
builder.RegisterType<SomeType>().As<IService>();

// 如果你想把元件自身也註冊成服務,那麼可以使用AsSelf()
builder.RegisterType<SomeType>().AsSelf().As<IService>();

對於我們的示例程式,我們需要註冊我們所有的元件(類)並公開它們的服務(介面),以便可以很好地連線起來。

我們還需要儲存容器,方便以後用它來解析型別。

using System;
using Autofac;

namespace DemoApp
{
  public class Program
  {
    private static IContainer _Container { get; set; }

    static void Main(string[] args)
    {
      var builder = new ContainerBuilder();
      
      builder.RegisterType<Output>().As<IOutput>();
      builder.RegisterType<TodayWriter>().As<ITodayWriter>();
      
      Container = builder.Build();
      
      //該方法我們後面實現它
      WriteDate();
    }
  }
}

現在我們有了一個容器其中所有元件都已註冊,並且它們公開了適當的服務。讓我們來使用它吧。

應用程式執行

在應用程式執行期間,我們需要使用我們註冊的元件。我們可以通過生命週期範圍解析它們來做到這一點。

容器本身就是一個生命週期範圍,從技術上講,我們可以直接從容器中解決問題。但是,我非常不建議直接從容器解析

當我們解析一個元件時,根據我們定義的例項範圍,會建立該物件的一個​​新例項。(解析一個元件大致相當於呼叫“new”來例項化一個類。這真的真的過於簡單化了,但從類比的角度來看,這個比較很恰當。)一些元件可能需要被釋放(就像它們實現了IDisposable一樣)——Autofac可以在生命週期範圍被釋放時自動為我們處理那些元件的釋放。

但是,容器在應用程式的整個生命週期內都存在。如果我們直接從容器中解決很多東西,等到最終,我們可能會有很多東西等待釋放。這非常的不好(如果我們這樣做,可能會發生“記憶體洩漏”的事故)。

相反,如果我們從容器建立一個子生命週期範圍並從中解析。當我們完成元件解析後,Autofac會幫我們自動釋放子作用域併為清理所有內容。

如果我們使用Autofac 整合庫時,這個子作用域的建立主要是自動為我們完成的,因此我們不必考慮它。

對於我們的示例應用程式,現在我們將實現“WriteDate”方法,它從一個子生命週期範圍獲取寫出結果,並在完成時自動處理該生命週期。

namespace DemoApp
{
  public class Program
  {
    private static IContainer Container { get; set; }

    static void Main(string[] args)
    {
      // 該內容在上面已經實現,不再重複
    }

    public static void WriteDate()
    {
      // 建立一個子生命週期範圍,解析ITodayWriter服務,呼叫方法,呼叫結束後被子生命週期範圍釋放掉
      using (var scope = Container.BeginLifetimeScope())
      {
        var writer = scope.Resolve<ITodayWriter>();
        writer.WriteDate();
      }
    }
  }
}

現在當我們執行程式時......

  • 該WriteDate方法建立了一個生命週期範圍,從中可以解析依賴項。這樣做是為了避免任何記憶體洩漏 - 如果ITodayWriter或其依賴項是一次性的,它們將在範圍被釋放時自動釋放。
  • 該WriteDate方法從生命週期範圍內手動解析ITodayWriter。(這裡是“服務地點”。)在內部……
    • Autofac 看到ITodayWriter對映到TodayWriter,所以開始建立一個TodayWriter.
    • Autofac 認為在其建構函式中TodayWriter需要一個IOutput。(這是“建構函式注入”。)
    • Autofac 看到IOutput對映到Output,所以建立一個新Output例項。
    • Autofac 使用新Output例項來完成構建TodayWriter.
    • Autofac 返回完全構造的TodayWriter供WriteDate消費。
  • 對writer.WriteDate()的呼叫會轉到全新的TodayWriter.WriteDate(),因為這是已解決的問題。
  • Autofac生命週期範圍已釋放。從該生命週期範圍解析的任何一次性項也將被處理。

之後,如果我們想讓我們的應用程式編寫一個不同的日期,我們可以實現一個不同的ITodayWriter,然後在應用程式啟動時更改註冊。我們不需要更改任何其他類。這是不是非常的棒,這就是控制反轉!

相關文章