处理批命令

时间:2019-04-12 11:18:41

标签: c# oop design-patterns architecture

我有一个类似this blog post中描述的架构。总而言之,我有命令对象,例如:

    LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional
LOCAL_PACKAGE_NAME := sss
LOCAL_PRIVILEGED_MODULE := true
LOCAL_CERTIFICATE := platform
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SRC_FILES := $(call all-java-files-under, src)

LOCAL_JAVA_LIBRARIES := telephony-common ims-common

LOCAL_STATIC_JAVA_LIBRARIES := \
    volley \
    gson \
    json

include $(BUILD_PACKAGE)

include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES=gson:libs/gson.jar
include $(BUILD_MULTI_PREBUILT)


include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES=json:libs/json.jar
include $(BUILD_MULTI_PREBUILT)
include $(BUILD_SHARED_LIBRARY)

从接口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
    }
}

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

选项1 void HandleBatch(List<ICommand> batch) { } 函数中放入一个庞大的开关盒。

HandleBatch

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

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;
      // ....
    }

  }
}

选项3 与选项2相同,但是还使用自定义注释以及在通过注释搜索类型过滤器时注释所有处理程序。

选项4 还有其他内容吗?

另一个不便之处是,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); } } 将必须方便地获取几乎所有可能依赖项的实例,因为应用程序的大多数逻辑都在这些命令中。但是我想我无法解决这个问题。

1 个答案:

答案 0 :(得分:0)

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

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语句或硬编码的解决方案。

此外,您可以在以后添加一个unregister方法,或者为给定命令保留一个以上的处理程序,限制是天空.. =)