前言
最近又在項目中碰到需要将原本單實作的接口改造成多個實作的場景,這裡記錄一下常見的幾種改法。
假設已經存在如下接口ICustomService和其實作CustomService,由于隻有一種實作,注入和使用非常容易。
public interface ICustomService{ void MethodA(); void MethodB();}public class CustomService: ICustomService{ public void MethodA() { } public void MethodB() { }} //注入builder.Services.AddTransient<ICustomService, CustomService>(); //使用private readonly ICustomService _customService;public CustomController(ICustomService customService){ _customService = customService;}
現在我們需要增加一種實作。
使用多個接口實作
我們可以将原ICustomService内的方法移到到一個新的基接口,共享出來,需要多少個實作,就建立多少個空接口繼承該基接口。
//基接口public interface ICustomBaseService{ void MethodA(); void MethodB();} //多個空接口public interface ICustomService : ICustomBaseService{} public interface ICustomServiceV2 : ICustomBaseService{} //第一種實作public class CustomService: ICustomService{ public void MethodA() { } public void MethodB() { }} //第二種實作public class CustomServiceV2: ICustomServiceV2{ public void MethodA() { } public void MethodB() { }} //注入builder.Services.AddTransient<ICustomService, CustomService>();builder.Services.AddTransient<ICustomServiceV2, CustomServiceV2>(); //使用private readonly ICustomService _customService;private readonly ICustomServiceV2 _customServiceV2;public CustomController(ICustomService customService,ICustomServiceV2 customServiceV2){ _customService = customService; _customServiceV2 = customServiceV2;}
這種實作方式需要增加了一套空接口做隔離,看似可能比較“浪費”,但後期随着項目的演進,ICustomService和ICustomServiceV2可能會慢慢分化,我們可以很友善的為它們擴充各種獨有方法。
使用單接口實作
如果我們确定不需要多個接口,也可以使用下面的單接口實作
public interface ICustomService{ void MethodA(); void MethodB();} //第一種實作public class CustomService: ICustomService{ public void MethodA() { } public void MethodB() { }} //第二種實作public class CustomServiceV2: ICustomService{ public void MethodA() { } public void MethodB() { }} //注入builder.Services.AddTransient<ICustomService, CustomService>();builder.Services.AddTransient<ICustomService, CustomServiceV2>(); //使用private readonly ICustomService _customService;private readonly ICustomService _customServiceV2;public CustomController(IEnumerable<ICustomService> customServices){ _customService = customServices.ElementAt(0); _customServiceV2 = customServices.ElementAt(1);}
從上面代碼可以看到,我們是為從接口ICustomService注冊兩個實作,并從IEnumerable<ICustomService>解析出了這兩個實作。這裡可能會有兩個疑問
- 為什麼第一個實作CustomService沒有被第二個實作CustomServiceV2替換掉?
- 為什麼可以從IEnumerable<ICustomService>解析到我們需要的服務?
答案在Microsoft.Extensions.DependencyInjection.ServiceDescriptor 和 Microsoft.Extensions.DependencyInjection.ServiceCollection 這兩個類裡,程序裡,依賴注入的服務,會被添加到ServiceCollection裡,ServiceCollection是一組ServiceDescriptor的集合,ServiceDescriptor通過服務類型、實作以及生命周期三個組合在一起構成的辨別來确定服務。而ICustomService+CustomService+Transient和ICustomService+CustomServiceV2+Transient是兩個不同的ServiceDescriptor,是以不會被替換。同時服務類型的ServiceDescriptor會被聚合在一起,于是我們可以很友善的從IEnumerable對象中解析出所有的同類型的服務。
總結
本質上,兩種方法都是多态性(Polymorphism)的應用,沒有優劣之分,根據場景選擇合适的寫法。
連結
https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
https://github.com/dotnet/runtime