前言
上一篇文章我們講到了Scrutor
第一個核心功能Scanning
,本文講解的是Scrutor
第二個核心的功能Decoration
裝飾器模式在依賴注入中的使用。
- 裝飾器模式允許您向現有服務類中新增新功能,而無需改變其結構
Install-Package Scrutor
本文的完整原始碼在文末
Decoration 依賴注入代理模式
首先首先一個 獲取 User 的服務
定義 User 類
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string? Email { get; set; }
}
定義介面和實現
public interface IUserService
{
List<User> GetAllUsers();
}
public class UserService : IUserService
{
public List<User> GetAllUsers()
{
Console.WriteLine("GetAllUsers方法被呼叫~");
List<User> users = [
new User(){
Id= 1,
Name="張三",
Age=18,
Email="zhangsan@163.com"
},
new User(){
Id= 2,
Name="李四",
Age=19,
Email="lisi@163.com"
},
];
return users!;
}
}
現在有了我們的獲取全部使用者的服務了,需求是在不破壞當前類的新增裝飾器模式,為 GetAllUsers
介面新增快取。
建立裝飾器類
public class UserDecorationService(IUserService userService, IMemoryCache cache) : IUserService
{
public List<User> GetAllUsers()
{
Console.WriteLine("GetAllUsers代理方法被呼叫~");
return cache.GetOrCreate("allUser", cacheEntry =>
{
cacheEntry.SetAbsoluteExpiration(
TimeSpan.FromMinutes(5));
var allUsers = userService.GetAllUsers();
return allUsers ?? [];
})!;
}
}
DI 容器新增服務
builder.Services.AddTransient<IUserService, UserService>();
builder.Services.AddMemoryCache();
builder.Services.Decorate<IUserService, UserDecorationService>();
建立介面測試一下
app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("獲取全部使用者介面");
呼叫第一次
GetAllUsers代理方法被呼叫~
GetAllUsers方法被呼叫~
第二次呼叫
GetAllUsers代理方法被呼叫~
可以看出第一次沒快取裝飾器類和我們 UserService 都呼叫了,第二次因為只有了快取所以只呼叫了裝飾器類,可以看出我們的裝飾器模式生效了。
依賴注入裝飾器底層核心實現
/// <summary>
/// Decorates all registered services using the specified <paramref name="strategy"/>.
/// </summary>
/// <param name="services">The services to add to.</param>
/// <param name="strategy">The strategy for decorating services.</param>
public static bool TryDecorate(this IServiceCollection services, DecorationStrategy strategy)
{
Preconditions.NotNull(services, nameof(services));
Preconditions.NotNull(strategy, nameof(strategy));
var decorated = false;
for (var i = services.Count - 1; i >= 0; i--)
{
var serviceDescriptor = services[i];
if (IsDecorated(serviceDescriptor) || !strategy.CanDecorate(serviceDescriptor))
{
continue;
}
var serviceKey = GetDecoratorKey(serviceDescriptor);
if (serviceKey is null)
{
return false;
}
// Insert decorated
services.Add(serviceDescriptor.WithServiceKey(serviceKey));
// Replace decorator
services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey));
decorated = true;
}
return decorated;
}
這個程式碼是在 dotNet8
的環境下編譯的,可以看出做了幾件事:
第一 IServiceCollection
集合倒序遍歷,找到符合條件的ServiceType
核心程式碼一
// Insert decorated
services.Add(serviceDescriptor.WithServiceKey(serviceKey));
將原先的ServiceDescription
作為基礎,新增了ServiceKey
後再進行新增操作,新的服務描述符會被新增到服務集合的末尾,
核心程式碼二
// Replace decorator
services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey));
這一步是將原有的服務描述符替換為一個新的服務描述符,新的服務描述符使用裝飾器工廠方法建立,實現了服務的裝飾功能。
用的時候
app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("獲取全部使用者介面");
這樣就可以獲取到裝飾器類提供服務,之前看到services.Add(serviceDescriptor.WithServiceKey(serviceKey));
在程式碼的最後新增了一個服務,那 IOC 獲取的時候肯定是從後面優先獲取,這地方用了 dotNet8
的鍵控依賴注入(KeyedService
),以 ServiceType
獲取服務只會獲取到我們提供的裝飾器例項,這一手簡直是神來之筆 👍。
最後
Scrutor
的裝飾器模式可以用於動態地給依賴注入的例項新增額外職責,實現動態增加和撤銷功能,而無需改變原有物件結構。可以在不影響其他物件的情況下,以透明且動態的方式給物件新增新功能,實現系統的靈活擴充套件和維護。
本文完整原始碼