我有一個涉及 DI 的奇怪案例,特別是在運行時從同一服務中決議實作時。我知道我可以注入一個服務提供者,但這似乎違反了依賴倒置原則。
此外,如果這最終成為更多的建筑/設計問題,我深表歉意;我最近從 .NET Framework 開發切換過來,但仍然熟悉 DI 的局限性。請注意,出于明顯的原因,我已經簡化并更改了業務環境,因此請記住,層次結構/結構是重要的部分...對于這個問題,我決定使用在線零售商的經典示例。
專案概述/示例:
核心庫(.NET Class Library)
- IRetailerService:客戶端應用使用的公共服務
└ IOrderService:注入到 ^ 中的門面/聚合服務
├ IInventoryManager:注入到門面/聚合服務以及其他組件中的內部組件
├ IProductRespository
└ IPriceEstimator
綜合/立面服務
public class RetailerService : IRetailerService
{
private readonly IOrderService _orderService;
public OrderService( IOrderService orderService, ... ) { //... set injected components }
async Task IRetailerService.Execute( Guid id )
{
await _orderService.Get( id );
}
async Task IRetailerService.Execute( Guid id, User user )
{
await _orderService.Get( id, user );
}
}
internal class OrderService : IOrderService
{
public OrderService( IInventoryManager inventoryManager, IProductRespository productRepo, ... ) { }
async Task<object> IOrderService.Get( Guid id )
{
//... do stuff with the injected components
await _inventoryManager.Execute( ...args );
await _productRepo.Execute( ...args );
}
async Task<object> IOrderService.Get( Guid id, User user ) { }
}
問題:
假設我想記錄IOrderService.Get( Guid id, User user ),但僅當提供了這種覆寫時User-這也包括在注入的組件(InventoryManager、IProductRepository 等)中進行記錄。
我目前能看到的唯一解決方案是:
- 向此層次結構添加一個附加層,并使用具有作用域生命周期的命名注冊來確定是否傳遞了 null 與日志記錄實作。
- Inject the service provider into the public facing service
IRetailerService, and somehow pass down the correct implementation.
I think my ideal solution would be some type of decorator/middleware to control this... I've only given the core library code; but there is also a WebApi project within the solution that references this library. Any ideas/guidance would be greatly appreciated.
uj5u.com熱心網友回復:
您可以IOrderService.Get在運行時決議方法中的依賴項,以便每個方法都有自己的依賴項。盡管如此,這并不能完全解決您的問題。嵌套的依賴項也IInventoryManager inventoryManager, IProductRespository productRepo, ...應該能夠啟用日志記錄。
因此,您可以使用:
internal class OrderService : IOrderService
{
public OrderService( IServiceProvider serviceProvider) { }
async Task<object> IOrderService.Get( Guid id )
{
var inventoryManager = (IInventoryManager)serviceProvider.GetService(typeof(IInventoryManager));
inventoryManager.Logging = false;
var productRepo = (IProductRespository)serviceProvider.GetService(typeof(IProductRespository));
productRepo.Logging = false;
//... do stuff with the injected components
await inventoryManager.Execute( ...args );
await productRepo.Execute( ...args );
}
async Task<object> IOrderService.Get( Guid id, User user ) {
var inventoryManager = (IInventoryManager)serviceProvider.GetService(typeof(IInventoryManager));
inventoryManager.Logging = false;
var productRepo = (IProductRespository)serviceProvider.GetService(typeof(IProductRespository));
productRepo.Logging = true;
//... do stuff with the injected components
await inventoryManager.Execute( ...args );
await productRepo.Execute( ...args );
}
}
您還可以為 Factory / Builder 提供一個引數以啟用日志記錄。但無論如何,因為您希望從同一根類開始的嵌套類中有不同的行為,所以這可能很復雜。
另一種選擇是提供 IOrderService 的 2 個實作,一個包括日志記錄,另一個不包括。但是我不確定這可能對您有所幫助,因為您可能有充分的理由為該方法提供多載而不是將它們拆分為單獨的服務。這并不能解決嵌套注入的問題。
最后一個選項可能是使用單例 LoggingOptions 類。每個依賴項都依賴于這個類,因為這是一個單例,每次你輸入你的多載時,你都將它設定為 true,所以所有類都會被告知你的記錄意圖。盡管如此,這在很大程度上取決于您的架構。如果可能幾乎同時呼叫這兩種方法,這可能會破壞嵌套的依賴關系日志記錄行為或隨時中斷日志記錄。
看看這個問題,這可能會有所幫助。通過考慮這個問題,您可以為每個依賴項(包括嵌套的依賴項)提供一個工廠,該工廠將在每次呼叫多載方法時設定日志記錄行為。
uj5u.com熱心網友回復:
我建議使用工廠來創建訂單服務,以及需要記錄器的任何下游依賴項。這是一個完整的示例:
void Main()
{
var serviceProvider = new ServiceCollection()
.AddScoped<IRetailerService, RetailerService>()
.AddScoped<IInventoryManager, InventoryManager>()
.AddScoped<IOrderServiceFactory, OrderServiceFactory>()
.BuildServiceProvider();
var retailerService = serviceProvider.GetRequiredService<IRetailerService>();
Console.WriteLine("Running without user");
retailerService.Execute(Guid.NewGuid());
Console.WriteLine("Running with user");
retailerService.Execute(Guid.NewGuid(), new User());
}
public enum OrderMode
{
WithUser,
WithoutUser
}
public interface IOrderServiceFactory
{
IOrderService Get(OrderMode mode);
}
public class OrderServiceFactory : IOrderServiceFactory
{
private readonly IServiceProvider _provider;
public OrderServiceFactory(IServiceProvider provider)
{
_provider = provider;
}
public IOrderService Get(OrderMode mode)
{
// Create the right sort of order service - resolve dependencies either by new-ing them up (if they need the
// logger) or by asking the service provider (if they don't need the logger).
return mode switch
{
OrderMode.WithUser => new OrderService(new UserLogger(), _provider.GetRequiredService<IInventoryManager>()),
OrderMode.WithoutUser => new OrderService(new NullLogger(), _provider.GetRequiredService<IInventoryManager>())
};
}
}
public interface IRetailerService
{
Task Execute(Guid id);
Task Execute(Guid id, User user);
}
public interface IOrderService
{
Task Get(Guid id);
Task Get(Guid id, User user);
}
public class User { }
public class RetailerService : IRetailerService
{
private readonly IOrderServiceFactory _orderServiceFactory;
public RetailerService(
IOrderServiceFactory orderServiceFactory)
{
_orderServiceFactory = orderServiceFactory;
}
async Task IRetailerService.Execute(Guid id)
{
var orderService = _orderServiceFactory.Get(OrderMode.WithoutUser);
await orderService.Get(id);
}
async Task IRetailerService.Execute(Guid id, User user)
{
var orderService = _orderServiceFactory.Get(OrderMode.WithUser);
await orderService.Get(id, user);
}
}
public interface ISpecialLogger
{
public void Log(string message);
}
public class UserLogger : ISpecialLogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class NullLogger : ISpecialLogger
{
public void Log(string message)
{
// Do nothing.
}
}
public interface IInventoryManager { }
public class InventoryManager : IInventoryManager { }
internal class OrderService : IOrderService
{
private readonly ISpecialLogger _logger;
public OrderService(ISpecialLogger logger, IInventoryManager inventoryManager)
{
_logger = logger;
}
public async Task Get(Guid id)
{
_logger.Log("This is the 'id-only' method");
}
public async Task Get(Guid id, User user)
{
_logger.Log("This is the 'id-and-user' method");
}
}
使用它,您將獲得以下輸出:
Running without user
Running with user
This is the 'id-and-user' method
工廠讓您可以完全控制下游組件的生成方式,因此您可以隨心所欲地變得復雜。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/323398.html
標籤:c# .net dependency-injection inversion-of-control .net-5
下一篇:“無法將型別為“Google.Android.Material.TextView.MaterialTextView”的實體轉換為型別“AndroidX.AppCompat.Widget.Toolbar
