我有一个依赖Command
接口的抽象基类ICommandLogger
:
public abstract class Command
{
public Command(ICommandLogger cmdLogger) { /* ... */ }
}
现在所有的继承人都是这样的:
public class ConcreteCommand : Command
{
public ConcreteCommand(CommandLoggersNamespace.ICommandLogger cmdLogger)
: base(cmdLogger)
{
}
}
我不喜欢他们被迫了解ICommandLogger
(他们不使用)。
如何解决这个问题?或者完全重新设计的原因是什么?
答案 0 :(得分:5)
您的设计中存在一个问题,导致您遇到这些麻烦。首先,日志记录是cross-cutting concern,您应该使用它来防止污染类。其次,如果让基类实现日志记录,那么将在logger基类上添加的下一个横切关注点是什么。第二个你开始添加另一个横切关注点,基类将违反Single Responsibility Principle。你的基类最终将成长为一个具有大量依赖关系的大型无法管理的类,并且有许多改变的原因。
相反,尝试将日志记录添加为装饰器。然而,你的设计会妨碍你有效地做到这一点,因为你可能会有几十个具体的命令,他们都需要自己的装饰器。但您设计的核心问题是混合数据和行为。让命令只是一个包含一些数据(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.
}
}
这个设计的有趣之处在于,由于所有业务逻辑现在都隐藏在一个narrow interface后面,并且这个接口是通用的,因此通过使用装饰器包装处理程序变得非常容易extend the behavior of the system。例如一个日志装饰器:
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>
),并且可以在不更改单行代码的情况下向所有业务逻辑添加横切关注点。
在大多数情况下,这将完全不需要使用基类。
您可以详细了解此类设计here。
答案 1 :(得分:2)
如果您通过构造函数进行依赖注入,则无法解决此问题。另一种方法是通过属性设置器进行依赖注入。就个人而言,我更喜欢构造函数方法,因为对我而言,这表示此类需要此依赖项,而注入属性的依赖项则传达可选的依赖项。
如果你去构造函数路由,并且你的基类需要很多依赖,你可以减轻creating an aggregate service的一些痛苦,这样基数只需要注入一个参数而不是很多。
答案 2 :(得分:1)
如果他们不使用命令记录器,您可能会尝试不设置任何一个,如下所示:
public ConcreteCommand()
: base(null)
{
}
如果这不起作用(抛出Exception
),您可能会尝试实现phony命令记录器并实例化该命令:
public ConcreteCommand()
: base(new MyPhonyCommandLogger())
{
}
如果您不想要这些虚假实例,请静态使用单个实例:
public ConcreteCommand()
: base(MyPhonyCommandLogger.Instance)
{
}
答案 3 :(得分:0)
我不喜欢他们被迫了解ICommandLogger(他们 不要使用)。
他们这样做;除非您在抽象类中实例化类型为ICommandLogger
的对象并提供无参数构造函数,否则您当前正在强制继承者了解它。
答案 4 :(得分:0)
另一种看待它的方式是ConcreteComand
类确实依赖ICommandLogger
。它从Command派生时继承了它。
因此除了ConcreteCommand
代表其基类接受依赖之外,没有任何方法可以解决你正在做的事情 - 想想它不是&#34; { {1}}有ConcreteCommand
&#34;但更喜欢&#34; Command
a ConcreteCommand
&#34;
考虑一下,如果你以某种方式能够以Command
的方式获得基类依赖性以及#34;偷偷过去&#34,那么你想要如何覆盖基类日志记录行为; ICommandLogger
...
如果你想提供&#34; base like&#34;功能(ConcreteCommand
)等)你绝对不希望base.Log("foo")
知道ConcreteComand
,那么你总是可以切换到&#34;有一个&#34;类型场景 - ICommandLogger
只是Command
的成员变量(在这种情况下愚蠢的理由恕我直言!)