[英]How to inject a dependency into a base class if child doesn't know it?
我有一個依賴於ICommandLogger
接口的抽象基類Command
:
public abstract class Command
{
public Command(ICommandLogger cmdLogger) { /* ... */ }
}
現在所有的繼承人看起來像:
public class ConcreteCommand : Command
{
public ConcreteCommand(CommandLoggersNamespace.ICommandLogger cmdLogger)
: base(cmdLogger)
{
}
}
我不喜歡他們被迫了解ICommandLogger
(他們不使用)。
怎么解決這個問題? 或者完全重新設計的原因是什么?
您的設計中存在一個問題,導致您遇到這些麻煩。 首先,伐木是一個貫穿各領域的問題 ,你應該防止污染類。 其次,如果讓基類實現日志記錄,那么將在logger基類上添加的下一個橫切關注點是什么。 第二個你開始添加另一個橫切關注點,基類將違反單一責任原則 。 你的基類最終將成長為一個具有大量依賴關系的大型無法管理的類,並且有許多改變的理由。
相反,嘗試添加日志作為裝飾器。 然而,你的設計會妨礙你有效地做到這一點,因為你可能會有幾十個具體的命令,他們都需要自己的裝飾器。 但您設計的核心問題是混合數據和行為。 讓命令只是一個包含一些數據(DTO)的類,並將命令邏輯添加到它自己的類中; 讓我們稱之為命令處理程序 。
最重要的是,讓命令處理程序實現此接口:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
這將是這樣的:
public class MoveCustomerCommand
{
public Guid CustomerId;
public Address NewAddress;
}
public class MoveCustomerCommmandHandler : ICommandHandler<MoveCustomerCommand>
{
public void Handle(MoveCustomerCommand command)
{
// behavior here.
}
}
這個設計的有趣之處在於,由於所有業務邏輯現在都隱藏在一個狹窄的接口后面,並且這個接口是通用的,因此通過使用裝飾器包裝處理程序來擴展系統的行為變得非常容易。 例如一個日志裝飾器:
public class LoggingCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> decoratee;
public LoggingCommandHandlerDecorator(
ICommandHandler<TCommand> decoratee, ILog log)
{
this.decoratee = decoratee;
this.log = log;
}
public void Handle(TCommand command)
{
this.log.Log("Executing " + typeof(TCommand).Name + ": " +
JsonConvert.Serialize(command));
try
{
this.decoratee.Handle(command);
}
catch (Exception ex)
{
this.log.Log(ex);
throw;
}
}
}
由於此LoggingCommandHandlerDecorator<TCommand>
是通用的,因此它可以包裝在任何ICommandHandler<TCommand>
周圍。 這允許您讓消費者依賴某些ICommandHandler<TCommand>
(例如ICommandHandler<MoveCustomerCommand>
),並且可以在不更改單行代碼的情況下向所有業務邏輯添加橫切關注點。
在大多數情況下,這將完全消除使用基類的需要。
您可以在此處閱讀有關此類設計的更多信息。
如果您通過構造函數進行依賴注入,則無法解決此問題。 另一種方法是通過屬性設置器進行依賴注入。 就個人而言,我更喜歡構造函數方法,因為對我來說,傳達此類需要此依賴項,而屬性注入的依賴項傳達可選的依賴項。
如果你去構造函數路由,並且你的基類需要很多依賴項,你可以通過創建聚合服務來減輕一些痛苦,這樣底層只需要注入一個參數而不是多個參數。
如果他們不使用命令記錄器,您可能會嘗試不設置任何,如下所示:
public ConcreteCommand()
: base(null)
{
}
如果這不起作用(拋出Exception
),您可以嘗試實現一個偽命令記錄器並實例化一個:
public ConcreteCommand()
: base(new MyPhonyCommandLogger())
{
}
如果您不想要這些虛假實例,請靜態使用單個實例:
public ConcreteCommand()
: base(MyPhonyCommandLogger.Instance)
{
}
我不喜歡他們被迫了解ICommandLogger(他們不使用)。
好吧,他們做; 除非您在抽象類中實例化ICommandLogger
類型的對象並提供無參數構造函數,否則您當前正在強制繼承者了解它。
另一種看待它的方法是ConcreteComand
類確實依賴於ICommandLogger
。 它從Command派生時繼承了它。
因此,除了ConcreteCommand
代表其基類接受依賴之外,沒有辦法解決你正在做的事情 - 想想它不是“ ConcreteCommand
有一個Command
”,而更像是“ ConcreteCommand
是一個Command
”
想想你如何處理想要覆蓋基類日志記錄行為,如果你能以某種方式得到他對ICommandLogger
基類依賴,以某種方式“偷偷摸摸” ConcreteCommand
的構建......
如果您希望能夠提供“類似基礎”功能( base.Log("foo")
)等,並且您絕對不希望ConcreteComand
了解ICommandLogger
,那么您始終可以切換到“有一個”類型的場景- 其中Command
只是ConcreteCommand
的成員變量(在這種情況下愚蠢的理由方法恕我直言!)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.