為 ServiceCollection 實現裝飾器模式
Intro
在二十四種設計模式中,有一個模式叫做裝飾器模式
一般用來動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更為靈活,有更好的擴展性,我們也可以借助 Decorator 來實現一個切面邏輯,實現 AOP 的一些功能
使用場景
裝飾模式是為已有功能動態地添加更多功能的一種方式
當系統需要新功能的時候,是向舊的類中添加新的代碼,這些新加的代碼通常裝飾了原有類的核心職責或主要行為,但是往往會在主類中加入新的字段/方法/邏輯,從而增加了主類的復雜度, 而這些新加入的東西僅僅是為了滿足一些只在某種特定情況下才會執行的特殊行為的需要
裝飾模式提供了一個很好的方案,它把每個要裝飾的功能放在單獨的類中,并讓這個類包裝它要裝飾的對象, 當需要執行特殊行為時,就可以在運行時根據需要有選擇地、按順序地使用裝飾功能包裝對象了。
裝飾模式的優點是把類中的裝飾功能從類中搬移去除,這樣可以簡化原有的類,這樣做就有效地把類的核心職責和裝飾功能區分開了,而且可以去除相關類中重復的裝飾邏輯。
Sample
直接來看示例效果吧:
首先我們定義了一個抽象接口 IJob
,接口里有一個 Name
屬性和一個 Execute
方法
然后定義了一個實現 Sleepy
實現了我們的 IJob
接口,
最后添加了一個裝飾器 JobDecorator
也實現了我們的 IJob
接口
private?interface?IJob
{string?Name?{?get;?}void?Execute();
}private?sealed?class?Sleepy?:?IJob
{public?string?Name?=>?nameof(Sleepy);public?void?Execute(){Console.WriteLine("Sleeping...");}
}private?sealed?class?JobDecorator?:?IJob
{private?readonly?IJob?_job;public?JobDecorator(IJob?job){_job?=?job;}public?string?Name?=>?$"????{_job.Name}";public?void?Execute(){Console.WriteLine("Before?execute");_job.Execute();Console.WriteLine("After?execute");}
}
接著我們看使用的示例吧:
var?services?=?new?ServiceCollection();
services.AddSingleton<IJob,?Sleepy>();
services.Decorate<IJob,?JobDecorator>();
using?var?sp?=?services.BuildServiceProvider();var?job?=?sp.GetRequiredService<IJob>();
Console.WriteLine(job.Name);
job.Execute();
輸出結果如下:
可以看到我們在原有實現的基礎上加入了我們裝飾器的邏輯,我們的 Before
和 After
也都執行了
Implement
Decorate
方法實現如下:
///?<summary>///?Register?decorator?for?TService///?</summary>///?<typeparam?name="TService">service?type</typeparam>///?<typeparam?name="TDecorator">decorator?type</typeparam>///?<param?name="services">services</param>///?<returns>services</returns>public?static?IServiceCollection?Decorate<TService,?TDecorator>(this?IServiceCollection?services)where?TService?:?classwhere?TDecorator?:?class,?TService{return?services.Decorate(typeof(TService),?typeof(TDecorator));}///?<summary>///?Register?service?decorator///?</summary>///?<param?name="services">services</param>///?<param?name="serviceType">serviceType</param>///?<param?name="decoratorType">decoratorType</param>///?<returns>services</returns>///?<exception?cref="InvalidOperationException">throw?exception?when?serviceType?not?registered</exception>public?static?IServiceCollection?Decorate(this?IServiceCollection?services,?Type?serviceType,?Type?decoratorType){var?service?=?services.FirstOrDefault(x?=>?x.ServiceType?==?serviceType);if?(service?==?null){throw?new?InvalidOperationException("The?service?is?not?registed,?service?need?to?be?registered?before?decorating");}var?objectFactory?=?ActivatorUtilities.CreateFactory(decoratorType,?new[]?{?serviceType?});var?decoratorService?=?new?ServiceDescriptor(serviceType,?sp?=>?objectFactory(sp,?new?object?[]{sp.CreateInstance(service)}),?service.Lifetime);services.Replace(decoratorService);return?services;}
實現邏輯主要是將原來這個 service 的注冊從原來的服務替換成 Decorator,Decorator 也實現了服務類型,是可以替換掉服務接口的注冊的,實現比較簡單,有些情況可能會有問題,主要是實現思路的分享
More
Github 上有一個關于 Decorator 的 issue,感興趣的看一下:https://github.com/dotnet/runtime/issues/36021
另外有一個開源項目 https://github.com/khellang/Scrutor 已經實現了基于 ServiceCollection 的 Decorator 支持,我們可以借助它來實現 Decorator 模式(原本以為只有服務注冊的擴展,偶然間發現還有 decorator 的實現)
使用示例:
var?collection?=?new?ServiceCollection();//?First,?add?our?service?to?the?collection.
collection.AddSingleton<IDecoratedService,?Decorated>();//?Then,?decorate?Decorated?with?the?Decorator?type.
collection.Decorate<IDecoratedService,?Decorator>();//?Finally,?decorate?Decorator?with?the?OtherDecorator?type.
//?As?you?can?see,?OtherDecorator?requires?a?separate?service,?IService.?We?can?get?that?from?the?provider?argument.
collection.Decorate<IDecoratedService>((inner,?provider)?=>?new?OtherDecorator(inner,?provider.GetRequiredService<IService>()));var?serviceProvider?=?collection.BuildServiceProvider();//?When?we?resolve?the?IDecoratedService?service,?we'll?get?the?following?structure:
//?OtherDecorator?->?Decorator?->?Decorated
var?instance?=?serviceProvider.GetRequiredService<IDecoratedService>();
感興趣的可以看一下實現,大體上和上面的思路是一樣的
References
https://greatrexpectations.com/2018/10/25/decorators-in-net-core-with-dependency-injection
https://github.com/dotnet/runtime/issues/36021
https://github.com/khellang/Scrutor
https://github.com/WeihanLi/WeihanLi.Common/blob/cdeac51a1fe65cddc98fe3d1fd1e43917e19aee4/samples/DotNetCoreSample/ServiceDecoratorTest.cs#L7