c# - 处理批量命令
问题描述
我有一个类似于这篇博文中描述的架构。总之,我有命令对象,例如:
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
由于应用程序的大部分逻辑都在这些命令中,因此必须有一个几乎所有可能依赖项的实例。但我想我不能绕过这个。
解决方案
好的。假设您有以下命令:
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 语句或硬编码的解决方案。
此外,您可以稍后添加一个取消注册方法,或者为给定命令保留多个处理程序,限制是天空.. =)
推荐阅读
- cakephp-3.0 - 保存复杂的多对多关联 CakePHP 4
- python - Python Pandas 添加 DataRow 修订号
- r - R如何根据以下行中的条件过滤数据框?
- sql - 来自嵌套 JSON 数组的 MarkLogic TDE XPath 值
- javascript - 获取新数组后组件未更新
- javascript - 我在 POSTMAN 中收到 500 个内部服务器错误
- c# - 从 Blazor Webassembly 流式传输大文件?
- javascript - 如何为我用 JavaScript 编写的这个 HTML 生成器添加适当的缩进
- regex - Perl - 如何从文本文件中省略行?
- elasticsearch - Elastic Search 多个 AND + OR 查询