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

Sam*_*der 3 c# generics dependency-injection decorator simple-injector

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

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

所以我的Dispatcher对象构造函数看起来像这样:

public CommandAndQueryDispatcher(IEnumerable<ICommandHandler> commandHandlers,
    IEnumerable<IQueryHandler> queryHandlers)
{
}
Run Code Online (Sandbox Code Playgroud)

我的CommandHandler接口如下所示:

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

public interface ICommandHandler
{
    void Execute(object command, ICommandAndQueryDispatcher dispatcher);
}
Run Code Online (Sandbox Code Playgroud)

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

public abstract class CommandHandlerBase<TCommand> : ICommandHandler<TCommand> 
    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<DeleteTeamCommand>
{
    public DeleteTeamCommandHandler(){        }

    public override void Execute(DeleteTeamCommand command, 
        ICommandAndQueryDispatcher dispatcher)
    {
       ... functionality here...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

为了让所有注入命令和查询的Dispatcher我让他们都有一个基础,genericless,接口ICommandHandlerIQueryHandler,然后询问实际收到的情况下(这是通用的),以获得他们处理命令的类型来注册它们,所以我可以稍后根据给定命令的类型查找处理程序.

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

我觉得我必须遗漏一些相当简单的东西.

所以我的问题是

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

要么

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

Ste*_*ven 8

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

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

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

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

相反,定义以下内容:

public interface ICommandHandler<TCommand> : where TCommand : ICommand
{
    void Execute(TCommand command);
}
Run Code Online (Sandbox Code Playgroud)

请注意以下几点:

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

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

// 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

container.RegisterSingle<ICommandDispatcher>(new CommandDispatcher(container));
Run Code Online (Sandbox Code Playgroud)

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