首页 > 解决方案 > 处理批量命令

问题描述

我有一个类似于这篇博文中描述的架构。总之,我有命令对象,例如:

public class MoveCustomerCommand : ICommand
{
    public int CustomerId { get; set; }
    public Address NewAddress { get; set; }
}

以及从接口派生的每个命令的命令处理程序ICommandHandler<TCommand>

public interface ICommandHandler<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomerCommand>
{
    public void Handle(MoveCustomerCommand command)
    {
        // Logic here
    }
}

现在我正在为以下用例寻找一个干净的解决方案:一些客户端生成了一批需要处理的异构命令。换句话说,我想实现以下内容:

void HandleBatch(List<ICommand> batch) {
}

我有一些想法,但我不相信其中任何一个都足够好。

选项 1在函数中放置一个巨大的开关盒HandleBatch

void HandleBatch(List<ICommand> batch) {
  foreach (var command in batch) {
    switch (command) {
      case MoveCustomerCommand cmd:
        new MoveCustomerCommandHandler().Handle(cmd);
        break;
      case DeleteCustomerCommand cmd:
        new DeleteCustomerCommandHandler().Handle(cmd);
        break;
      // ....
    }

  }
}

选项 2使用反射为每个命令找到适当的命令处理程序。

void HandleBatch(List<ICommand> batch) {
  foreach (var command in batch) {
    var commandType = command.GetType();

    var handlerInterface = typeof(ICommandHandler<>)
      .MakeGenericType(new Type[]{commandType});

    // Search the current assembly for a type that implements "handlerInterface" 
    var handlerType = Assembly.GetAssembly(this.GetType())
                .GetTypes()
                .Where(t => t != handlerInterface  &&
                    handlerInterface.IsAssignableFrom(t)
                ).First();

    var handler = CreateInstance(handlerType);
    handler.Handle(command);
  }
}

选项 3与选项 2 相同,但也使用自定义注释对所有处理程序进行注释,并且在通过注释搜索类型过滤器时也是如此。

选项 4还有什么?

另一个不便之处在于,HandleBatch由于应用程序的大部分逻辑都在这些命令中,因此必须有一个几乎所有可能依赖项的实例。但我想我不能绕过这个。

标签: c#oopdesign-patternsarchitecture

解决方案


好的。假设您有以下命令:

public class MoveCustomerCommand : ICommand
    {
        public int CustomerId { get; set; }

        public bool CanExecute(object parameter) => true;

        public void Execute(object parameter) { }


        public event EventHandler CanExecuteChanged;
    }

public class KillCustomerCommand : ICommand
    {
        public int CustomerId { get; set; }

        public bool CanExecute(object parameter) => true;

        public void Execute(object parameter) { }


        public event EventHandler CanExecuteChanged;
    }

现在考虑处理程序的以下架构建议:

 public abstract class CommandHandlerBase
    {
        protected static readonly Dictionary<Type, CommandHandlerBase> _handlers = new Dictionary<Type, CommandHandlerBase>();

        protected abstract void HandleCommand<TCommand>(TCommand command) where TCommand: ICommand;

        public static void Handle<TCommand>(TCommand command) where TCommand : ICommand
        {
            if (_handlers.TryGetValue(typeof(TCommand), out var handler))
            {
                handler.HandleCommand(command);
            }
        }
    }

    public abstract class CommandHandlerBase<TCommandHandlerBase, TCommand> : CommandHandlerBase
        where TCommandHandlerBase : CommandHandlerBase<TCommandHandlerBase, TCommand>, new() where TCommand : ICommand
    {
        public static void Register()
        {
            var type = typeof(TCommand);
            _handlers[type] = new TCommandHandlerBase();
        }

        protected override void HandleCommand<T>(T command) => Handle((TCommand) (object) command);

        public abstract void Handle(TCommand command);
    }

基本上我们所做的是将所有处理集中到一个基类中,并且我们只提供一个处理任何处理的入口点TCommand(前提是注册了一个处理程序,您可以放置​​一个默认情况,或者如果没有找到处理程序就崩溃)。

乍一看,实现可能看起来令人困惑,但使用后真的很好:我们只定义我们的处理程序类,然后调用Register. 让我们看看它们的外观:

public class MoveCustomerCommandHandler : CommandHandlerBase<MoveCustomerCommandHandler, MoveCustomerCommand>
    {
        public override void Handle(MoveCustomerCommand command) => Console.WriteLine("Moving the customer");
    }

    public class KillCustomerCommandHandler : CommandHandlerBase<KillCustomerCommandHandler, KillCustomerCommand>
    {
        public override void Handle(KillCustomerCommand command) => Console.WriteLine("Killing the customer");
    }

和测试:

private static void Main(string[] args)
{
    MoveCustomerCommandHandler.Register();
    KillCustomerCommandHandler.Register();
    CommandHandlerBase.Handle(new MoveCustomerCommand());
    CommandHandlerBase.Handle(new KillCustomerCommand());
    Console.ReadLine();
}

我认为这种方法更易于维护和扩展,不需要反射(性能受到影响),不需要非常大的 switch 语句或硬编码的解决方案。

此外,您可以稍后添加一个取消注册方法,或者为给定命令保留多个处理程序,限制是天空.. =)


推荐阅读