1

我目前正在为我正在设计的服务实现命令处理程序模式,其中命令本质上是处理程序.Handle()方法的 DTO。当我开始实现各种具体的类时,我意识到为了满足开放/封闭原则和单一职责原则,我最终可能会得到数千个 Command 和 Handler 类,这将严重违反 Don't Repeat Yourself 原则。

例如,我封装的部分过程需要ProjectId从 60 个奇数表中删除所有数据以重置它们。如果我将每个类都实现为原子具体的 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 例 switch 语句中得到的气味了。

苏……谁赢了?DRY 还是 SRP 和 OCP?

4

3 回答 3

2

不要太拘泥于首字母缩略词。专注于编写感觉正确的代码。原子命令是一个非常好的主意,但是您需要正确的粒度级别我通常认为命令是一个完整的(用户)操作。

您的枚举和上帝开关的设计未通过基本的健全性测试,并且在不修改类本身的情况下不可扩展,所以它一定很糟糕,对吧?

于 2014-04-22T15:44:57.600 回答
0

考虑使用 RelayCommand:http: //msdn.microsoft.com/en-us/magazine/dn237302.aspx

这是一个实现 ICommand 的命令,但希望为实际工作注入一个委托。许多 MVVM 框架包括开箱即用的 RelayCommand(或 DelegateCommand)。

因此,您实现了命令接口,并要求将 Action 或 Action<T> 注入到ctor中。执行触发动作。如果您需要将某些内容传递给操作,您可以使用“ofT”版本,或者将其包含在您传递的委托中。

这使您可以:

  • 有一个 Command 实现(或者 2 个,如果你支持泛型)
  • 将实际的命令逻辑放在其他地方(例如在您的域对象中)
  • 如果有意义,命令逻辑实际上可以是您的域类的 Private 成员,因为您传递了委托,所以由命令公开。

例子:

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);
  }
}
于 2014-04-22T16:21:22.307 回答
0

数百个命令和处理程序既不违反 DRY 也不违反 OCP,因为命令包含特定用例的命令,即每个命令和处理程序都在实现一个业务用例。您是否有相同的业务用例?

例如,我开发了一个具有不同资源类型的应用程序。但我只有 1 个 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的原理有点模糊

于 2014-04-23T16:23:24.600 回答