你的设计存在问题,导致你遇到了这些麻烦。首先,日志记录是一个
横切关注点,你应该防止将其污染到类中。其次,如果让基类实现日志记录,那么下一个添加到日志记录器基类上的横切关注点是什么?一旦你开始添加另一个横切关注点,基类就会违反
单一职责原则。你的基类最终会变成一个庞大且难以管理的类,有很多依赖和很多需要更改的原因。
相反,尝试将日志记录作为装饰器添加。但是,你的设计阻止你有效地这样做,因为你可能会有几十个具体命令,它们都需要自己的装饰器。但你的设计核心问题在于混合数据和行为。让命令只是包含一些数据(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)
{
}
}
这个设计的有趣之处在于,由于所有业务逻辑现在都隐藏在一个
简化接口 后面,而且这个接口是通用的,因此通过使用装饰器将处理程序包装起来,可以非常容易地
扩展系统的行为。例如,一个记录日志的装饰器:
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>
),并且可以在不改变一行代码的情况下向所有业务逻辑添加横切关注点。
在大多数情况下,这将完全消除使用基类的需要。
您可以在
这里阅读有关此类设计的更多信息。
ConcreteCommand
确实知道日志记录器。只是这种行为是继承而来的。 - dcastro