为通用接口配置装饰器,并使用Simple Injector中的非通用接口参数将所有实例注入构造函数

我一直在使用与这篇优秀文章中描述的模式非常相似的模式,将命令和查询作为对象。 我也使用SimpleInjector作为DI容器。

唯一显着的区别是,控制器对某些ICommandHandler采取显式依赖关系。我希望控制器依赖于一个对象(一个Dispatcher ),该对象将接受一个ICommand实例并解析该命令的正确处理程序。 这将减少构造函数需要采用的参数数量,并使整个事物更容易使用。

所以我的Dispatcher对象构造函数如下所示:

 public CommandAndQueryDispatcher(IEnumerable commandHandlers, IEnumerable queryHandlers) { } 

我的CommandHandler接口如下所示:

 public interface ICommandHandler : ICommandHandler where TCommand : ICommand { void Execute(TCommand command, ICommandAndQueryDispatcher dispatcher); } public interface ICommandHandler { void Execute(object command, ICommandAndQueryDispatcher dispatcher); } 

典型的命令处理程序如下所示:

 public abstract class CommandHandlerBase : ICommandHandler where TCommand : ICommand { public abstract void Execute(TCommand command, ICommandAndQueryDispatcher dispatcher); public void Execute(object command, ICommandAndQueryDispatcher dispatcher) { Execute((TCommand) command, dispatcher); } } internal class DeleteTeamCommandHandler : CommandHandlerBase { public DeleteTeamCommandHandler(){ } public override void Execute(DeleteTeamCommand command, ICommandAndQueryDispatcher dispatcher) { ... functionality here... } } 

然而,这个变化有一些敲门声,现在我想在我的命令和查询中添加一些装饰器,我遇到了一些问题。

为了将所有命令和查询注入Dispatcher我使它们都有一个基本的,无通用的接口ICommandHandlerIQueryHandler ,然后查询实际接收的实例(这是通用的)以获得它们处理的命令类型以进行注册所以我可以稍后根据给定命令的类型查找处理程序。

现在,当我尝试使用示例中指示的装饰器时,我似乎无法将任何内容注入到我的Dispatcher ,因为装饰实例被注册为generics类型,因此不要将其解析为基本的ICommandHandler实例。 如果我尝试使装饰器非通用,那么注入的实例没有任何generics类型参数,所以我找不到它的处理程序的命令类型。

我觉得我必须错过一些相当简单的东西。

所以我的问题是

  • 如何从容器转换中获取开放generics类型的所有实例作为传递给我的Dispatcher的基本接口?

要么

  • 有没有更好的方法来实现调度程序function,以便我的控制器可以不知道哪个处理程序将处理与SimpleInjector更好地运行的命令/查询?

这将减少构造函数需要采用的参数数量,并使整个事物更容易使用

请密切注意这一点,因为通过这样做,你可能会隐藏你的控制器做得太多的事实; 违反单一责任原则 。 SRP违规往往会导致可维护性问题。 甚至还有一篇你所引用文章的作者的后续文章 (那是我的btw),其中说明:

我当然不会提倡ICommandProcessor [在你的情况下是ICommandAndQueryDispatcher]来执行命令 – 消费者不太可能依赖于许多命令处理程序,如果他们这样做,可能会违反SRP。 ( 来源 )

本文甚至讨论了查询的解决方案,但您也可以将它应用于您的命令。 但是您应该考虑剥离解决方案并删除非通用接口。 你不需要它们。

相反,定义以下内容:

 public interface ICommandHandler : where TCommand : ICommand { void Execute(TCommand command); } 

请注意以下几点:

  1. 没有非通用接口。
  2. 您没有通过ICommandAndQueryDispatcher 。 那只是丑陋的。 ICommandAndQueryDispatcher是一个服务和服务需要通过构造函数注入传递。 另一方面,该command是运行时数据, 运行时数据通过方法参数传递 。 因此,如果有一个需要调度程序的命令或查询处理程序:通过构造函数注入它。
  3. TCommand没有关键字。 由于命令是用例,因此命令和命令处理程序实现之间应该存在一对一的映射。 但是,指定’in’意味着一个命令类可以映射到多个处理程序,但情况并非如此。 另一方面,在处理事件和事件处理程序时,这将是一个更加明显的方法。
  4. 不再有CommandHandlerBase 。 你不需要那个。 我认为好的设计几乎不需要基类。

另外,不要尝试将命令调度程序与查询混合使用。 两个职责意味着两个class级。 这是您的命令调度程序的外观:

 // This interface is defined in a core application layer public interface ICommandDispatcher { void Execute(ICommand command); } // This class is defined in your Composition Root (where you wire your container) // It needs a dependency to the container. sealed class CommandDispatcher : ICommandDispatcher { private readonly Container container; public CommandDispatcher(Container container) { this.container = container; } public void Execute(ICommand command) { var handlerType = typeof(ICommandHandler<>) .MakeGenericType(command.GetType()); dynamic handler = container.GetInstance(handlerType); handler.Handle((dynamic)command); } } 

请注意,您不会在此处注入一组命令处理程序,而是从容器中请求处理程序。 此代码仅包含基础结构且没有业务逻辑,因此如果将此实现放在负责连接容器的代码附近,则不会滥用Service Locator反模式 ,这是一种有效的方法。 在这种情况下,应用程序的其余部分仍然不依赖于DI框架。

您可以按如下方式注册此CommandDispatcher

 container.RegisterSingle(new CommandDispatcher(container)); 

如果采用这种方法,因为您通过ICommandHandler接口请求处理程序,容器将自动用任何必须根据您的配置和您应用的generics类型约束应用的装饰器包装处理程序。