配置通用接口装饰,并与简单的注射非通用接口参数注入所有实例构造 [英] Configure decorators for generic interfaces and inject all instances to constructor with non generic interface argument in Simple Injector

查看:213
本文介绍了配置通用接口装饰,并与简单的注射非通用接口参数注入所有实例构造的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在使用非常相似,被描述的在这个优秀的文章有命令和查询的对象。 。我也是用SimpleInjector作为DI容器



唯一显著的区别是,宁可控制器采取一些 ICommandHandler℃的显式依赖; TCommand> 我要控制器采取的对象的依赖关系(A 调度),这将需要一个的ICommand 实例,解析该命令的正确处理程序。这将减少构造函数需要采取,使整个事情变得更容易使用的参数的数量。



所以,我的调度对象的构造是这样的:

 公共CommandAndQueryDispatcher(IEnumerable的< ICommandHandler> commandHandlers,
的IEnumerable< IQueryHandler> queryHandlers)
{
}

和我CommandHandler接口看起来像这样

 公共接口ICommandHandler<在TCommand> :ICommandHandler 
式TCommand:ICommand的
{
无效执行(TCommand命令,ICommandAndQueryDispatcher调度员);
}

公共接口ICommandHandler
{
无效执行(对象命令,ICommandAndQueryDispatcher调度员);
}

和一个典型的命令处理程序如下:

 公共抽象类CommandHandlerBase< TCommand> :ICommandHandler< TCommand> 
式TCommand:ICommand的
{
公共抽象无效执行(TCommand命令,ICommandAndQueryDispatcher调度员);

公共无效执行(对象命令,ICommandAndQueryDispatcher调度)
{
执行((TCommand)命令,调度员);
}
}

内部类DeleteTeamCommandHandler:CommandHandlerBase< DeleteTeamCommand>
{
公共DeleteTeamCommandHandler(){}

公共覆盖无效执行(DeleteTeamCommand命令,
ICommandAndQueryDispatcher调度员)
{
...功能在这里...
}
}

不过这种变化有一些knockons现在我想添加一些装饰添加到我的命令,我已经有一些问题的查询。



为了让所有注入<$命令和查询C $ C>调度我让他们都有一个基础,genericless,接口 ICommandHandler IQueryHandler ,然后询问实际收到的情况下(这是通用的),以获得他们处理登记他们,所以我可以看一下处理器基于稍后给出命令的类型命令的类型。



现在,当我尝试和使用的装饰似乎在实例中指出,我不能得到任何东西注射到我的调度,作为装饰实例注册为通用类型,所以没有得到解决为基本 ICommandHandler 实例。如果我尝试使装饰非通用然后这些注入的情况下,没有任何泛型类型参数,所以我找不到什么类型的命令它的处理程序。



我觉得我必须失去了一些东西很简单。



所以我的问题是不是




  • 我怎样才能从转换为传递到我的调度

  • 的基本接口容器中的开放式泛型类型的所有实例





  • 有没有为我实现调度功能,更好的办法使这些处理程序将要处理的命令/查询它与SimpleInjector扮演更漂亮我控制器可以是无知?


解决方案

这将减少该构造需要
承担,使整个事情变得更轻松使用


参数的数量

请就这个密切关注,因为这样做,你可能会隐瞒事实,你的控制器做太多;违反单一职责原则。 SRP的侵犯往往会导致可维护性的问题以后。甚至还有一个跟进你的文章的作者的文章指(这是我的BTW)指出:




我当然不主张一个ICommandProcessor [这是你的情况
ICommandAndQueryDispatcher对于执行命令 -
的消费者不太可能采取许多命令
处理的依赖,如果他们这样做很可能会违反SRP的。 (




文章甚至讨论了本作的查询解决办法,但你可以把它应用到你的命令,以及。但是,你应该考虑剥离您的解决方案并删除非通用接口。你不需要他们。



相反,定义如下:

 公共接口ICommandHandler< TCommand> :其中TCommand:ICommand的
{
无效执行(TCommand命令);
}



待办事项几件事情:




  1. 有没有非通用接口。

  2. 您没有通过 ICommandAndQueryDispatcher 。这只是丑陋。在 ICommandAndQueryDispatcher 是一个服务和服务需要通过构造器注入传递。在命令,另一方面是运行时数据,和的运行时数据是通过使用方法参数传递的。所以,如果有需要的调度员命令或查询处理程序:注入它通过构造

  3. 有没有关键字为 TCommand 。由于命令是使用情况下,存在的命令和命令处理程序实现之间的一对一的映射。指定一个'在'然而意味着一个命令类可以映射到多个处理程序,但是这不应该是这样的。当与另外的事件和事件处理程序处理,这将是一个更加明显的方法

  4. 不再 CommandHandlerBase< TCommand> 。你并不需要这一点。我认为一个好的设计未落需要一个基类。



另一件事,不要试图为混合命令调度与用于查询。两个责任是指两班。这是你的命令发送的外观:

  //这个接口是在核心应用层
公共接口定义ICommandDispatcher
{
无效执行(ICommand的命令);
}

//这个类是在你的作文根定义(您连线的容器)
//它需要依赖于容器。
密封类CommandDispatcher:ICommandDispatcher
{
私人只读集装箱货柜;

公共CommandDispatcher(集装箱货柜){
this.container =容器;
}

公共无效执行(ICommand的命令){
VAR handlerType = typeof运算(ICommandHandler<>)
.MakeGenericType(command.GetType());

动态处理函数= container.GetInstance(handlerType);

handler.Handle((动态)命令);
}
}



请注意,您不注射的集合命令处理程序在这里,而是从容器请求的处理程序。该代码将只包含基础设施,没有业务逻辑,所以如果你接近这个地方实施,负责接线的容器,你会的滥用的服务定位器反模式,这是一种有效的方法。在这种情况下,应用程序的其余部分仍然不依赖于DI框架。



您可以注册这个 CommandDispatcher 如下:

  container.RegisterSingle< ICommandDispatcher>(新CommandDispatcher(集装箱)); 

如果你采用这种方法,因为你的 ICommandHandler<请求的处理程序; TCommand方式> 接口,容器将必须根据自己的配置,你的应用泛型类型的限制被应用于任何装饰自动换行处理程序


I've been using a pattern very similar to what is described in this excellent article to have commands and queries as objects. I am also using SimpleInjector as the DI container.

The only significant difference is that rather that controller take an explicit dependency on some ICommandHandler<TCommand> I want the controllers to take a dependency on an object (a Dispatcher) which will take a ICommand instance and resolve the correct handler for that command. This will reduce the number of parameters that the constructors need to take and make the whole thing a little easier to use.

So my Dispatcher objects constructor looks like this:

public CommandAndQueryDispatcher(IEnumerable<ICommandHandler> commandHandlers,
    IEnumerable<IQueryHandler> queryHandlers)
{
}

and my CommandHandler interfaces look like this:

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

public interface ICommandHandler
{
    void Execute(object command, ICommandAndQueryDispatcher dispatcher);
}

And a typical command handler looks like:

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

However this change had some knockons and now I want to add some decorators to my commands and queries I've having some problems.

In order to have all of the commands and queries injected into the Dispatcher I made them all have a base, genericless, interface ICommandHandler and IQueryHandler, then interrogate the instances actually received (which are generic) to get the type of command they handle to register them so I can look up the handler based on the type of the given command later on.

Now when I try and use decorators as indicated in the examples I can't seem to get anything injected into my Dispatcher, as the decorated instances are registered as generic types, so don't get resolved as basic ICommandHandler instances. If I try making the decorators non generic then the instances which are injected don't have any generic type parameters so I can't find what type of command its a handler for.

I feel like I must be missing something fairly simple.

So my question is either

  • How can I get all instances of an open generic type from the container cast as a base interface passed into my Dispatcher?

OR

  • Is there a better way for me to implement the dispatcher functionality so that my controllers can be ignorant of which handler is going to handle a command/query which plays more nicely with SimpleInjector?

解决方案

This will reduce the number of parameters that the constructors need to take and make the whole thing a little easier to use

Please take a close watch on this, because by doing this, you might hide the fact that your controllers do too much; violate the Single Responsibility Principle. SRP violations tend to lead to maintainability issues later on. There's even a follow up article of the author of the article you refer to (that's me btw) that states:

I certainly wouldn’t advocate an ICommandProcessor [that's ICommandAndQueryDispatcher in your case] for executing commands - consumers are less likely to take a dependency on many command handlers and if they do it would probably be a violation of SRP. (source)

The article even discusses a solution for this for queries, but you can apply it to your commands as well. But you should consider stripping your solution down and remove the non-generic interfaces. You don't need them.

Instead, define the following:

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

Do note a few things:

  1. There's no non-generic interface.
  2. You don't pass through the ICommandAndQueryDispatcher. That's just ugly. The ICommandAndQueryDispatcher is a service and services need to be passed through constructor injection. The command on the other hand is runtime data, and runtime data is passed through using method arguments. So if there's a command or query handler that needs the dispatcher: inject it through the constructor.
  3. There's no in keyword for TCommand. Since commands are use cases, there should be a one-to-one mapping between a command and a command handler implementation. Specifying an 'in' however means that one command class can map to multiple handlers, but this should not be the case. When dealing with events and event handlers on the other hand, this will be a much more obvious approach.
  4. No more CommandHandlerBase<TCommand>. You don't need that. I would argue that a good design hardly ever needs a base class.

Another thing, don't try to mix the dispatcher for commands with that for the queries. Two responsibilities means two classes. This is how your command dispatcher will look:

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

Do note that you don't inject a collection of command handlers here, but instead to request a handler from the container. This code will only contain infrastructure and no business logic, so if you place this implementation close to the code that is responsible of wiring the container, you will not abuse the Service Locator anti-pattern, and this is a valid approach. The rest of the application in that case still doesn't depend on the DI framework.

You can register this CommandDispatcher as follows:

container.RegisterSingle<ICommandDispatcher>(new CommandDispatcher(container));

If you take this approach, because you request a handler by the ICommandHandler<TCommand> interface, the container will automatically wrap the handlers with any decorators that must be applied according to your configuration and the generic type constraints you applied.

这篇关于配置通用接口装饰,并与简单的注射非通用接口参数注入所有实例构造的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆