我目前正在为服务实现命令处理程序模式,我设计的命令实际上是Handler .Handle()
方法的DTO。当我开始实施各种具体课程时,我意识到为了满足开放/封闭原则和单一责任原则,我可能最终会有成千上万的Command和Handler课程,这将严重违反不要重复自己的原则。
例如,我封装的部分过程需要从60个奇数表中删除ProjectId
的所有数据以重置它们。如果我将每个实现为一个原子具体的Command对象和具体的CommandHandler对象,那么我将在第一步中拥有120个类。 他们都将完全遵循SRP& OCP,但DRY受到严重打击......
public class DeleteProjectLogCommand : CommandBase
{
public long? ProjectId { get; set; }
}
public class DeleteProjectLogCommandHandler : ICommandHandler<DeleteProjectLogCommand>
{
public async Task<Feedback<DeleteProjectLogCommand>> Handle(DeleteProjectLogCommand command, CancellationToken token)
{
// ...
}
}
或者,我可以实现一个单一的,多用途的命令和处理程序类,并且可以使用ProjectTables
枚举来代替所有离散类。
public class DeleteTableByProjectIdCommand : CommandBase
{
public DeleteTableByProjectIdCommand(ProjectTables table, long? projectId) {}
public long? ProjectId { get; set; }
public ProjectTables Table { get; set; }
}
public class DeleteTableByProjectIdCommandHandler : ICommandHandler<DeleteTableByProjectIdCommand>
{
public async Task<Feedback<DeleteTableByProjectIdCommand>> Handle(DeleteTableByProjectIdCommand command, CancellationToken token)
{
switch(command.Table)
{
case ProjectTables.ProjectLog:
// x60 tables
break;
}
}
}
然而,这会违反开放/封闭原则,因为如果添加新表,则枚举和使用它的每个地方都必须更新。更不用说从60个案例的开关声明中得到的气味。
Sooo ......谁赢了? DRY或SRP&amp; OCP?
答案 0 :(得分:1)
你的enum和God开关的设计没有通过基本的健全性测试,如果没有修改类本身就无法扩展,所以一定是坏的,对吗?
答案 1 :(得分:0)
考虑使用RelayCommand:http://msdn.microsoft.com/en-us/magazine/dn237302.aspx
这是一个实现ICommand的命令,但是希望为实际工作注入一个委托。许多MVVM框架都包含一个开箱即用的RelayCommand(或DelegateCommand)。
因此,您实现了命令接口,并要求Action或Action&lt; T&gt;注入ctor。执行会触发操作。如果您需要将某些内容传递给操作,则可以使用“ofT”版本,或将其包含在您传递的代理中。
这允许您:
示例:
public class SomeViewModelOrDomainClass
{
public ICommand DoItCommand {get; private set;}
//ctor
public SomeViewModelOrDomainClass()
{
// if your command includes a CanExecute bool, then also demand a Predicate to handle CanExecute
this.DoItCommand = new RelayCommand(() => this.SomePrivateMethod(maybeEvenAnEnclosedParam), aCanExecutePredicate);
}
}
答案 2 :(得分:0)
数百个命令和处理程序都不会违反DRY,因为命令包含特定用例的命令,即每个命令和处理程序都在实现业务用例。你有相同的商业用例吗?
一个例子,我在一个具有不同资源类型的应用程序上工作。但我只有一个看起来像这样的DeleteCommand
public class DeleteResource:AbstractCommand
{
public Guid ResourceId;
public ResourceType Type;
}
public class DeleteResourceHandler:IExecute<DeleteResource>
{
private IDispatchMessages _bus;
private IStoreResources _repo;
public DeleteResourceHandler(IStoreResources repo, IDispatchMessages bus)
{
_repo = repo;
_bus = bus;
}
public void Execute(DeleteResource cmd)
{
_repo.Delete(cmd.ResourceId);
var evnt = cmd.ToEvent();
_bus.Publish(evnt);
}
}
当然这不是全部,因为整个应用程序设计用于 N 资源类型,这意味着我的实体存储主要是一个不关心的键值存储实体结构。删除只需要一个id。
删除资源后,将发布事件,该事件由读取模型更新程序处理,然后删除该资源类型的查询数据。
添加新资源时,我不需要触摸DeleteCommand或DeleteHandler,甚至也不需要触摸实体存储。但是你看,命令和处理程序不能单独工作,它们正在使用其他组件来实现DRY和OCP。 OCP有点fuzzy principle。