简单的喷油器 - 注册装饰用同一个通用类型的另一种依赖 [英] Simple Injector - Register decorator with another dependency of same generic type

查看:148
本文介绍了简单的喷油器 - 注册装饰用同一个通用类型的另一种依赖的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在在简单的喷油器的一个怪癖我已经迷迷糊糊 RegisterDecorator()。它发生,即使在最近的2.5.0。我有一个情况下,我想装饰一个封闭泛型类型,例如 ICommandHandler< MessageCommand> ,与装饰的需要类型的(通过构造函数注入)的内部处理程序 ICommandHandler< MessageCommand> ,但另一种类型的处理程序,比如 ICommandHandler< LogCommand> 。尽管这些命令处理程序的类型是不同的,SimpleInjector似乎感到困惑,并抛出当我打电话异常 RegisterDecorator 这样的装饰类型:

I think I have stumbled upon a quirk in Simple Injector's RegisterDecorator(). It occurs even in the most recent 2.5.0. I have a situation where I want to decorate a closed generic type, for example ICommandHandler<MessageCommand>, with a decorator that takes (via constructor injection) an inner handler of type ICommandHandler<MessageCommand>, but also another type of handler, say ICommandHandler<LogCommand>. Even though these command handler types are distinct, SimpleInjector seems to get confused and throws an exception when I call RegisterDecorator on such a decorator type:

ArgumentException的:对于容器能够使用MessageLogger作为一个装饰,它的构造函数必须包含类型的单个参数 ICommandHandler&LT; MessageCommand&GT; (或函数功能:LT; ICommandHandler&LT; MessageCommand&GT;&GT; ) - 即是正在装修的实例的类型。参数类型 ICommandHandler&LT; MessageCommand&GT; 在类MessageLogger的构造函数定义多次

ArgumentException: For the container to be able to use MessageLogger as a decorator, its constructor must include a single parameter of type ICommandHandler<MessageCommand> (or Func<ICommandHandler<MessageCommand>>) - i.e. the type of the instance that is being decorated. The parameter type ICommandHandler<MessageCommand> is defined multiple times in the constructor of class MessageLogger.

...即使装饰显然只有一个 ICommandHandler&LT; MessageCommand&GT; 参数

... even though the decorator clearly only has one ICommandHandler<MessageCommand> parameter.

下面是完整的工作示例抛出异常:

Here is the full working example that throws the exception:

public interface ICommandHandler<T>
{
    void Execute(T command);
}

public class LogCommand
{
    public string LogMessage { get; set; }
    public DateTime Time { get; set; }
}

public class Logger : ICommandHandler<LogCommand>
{
    public void Execute(LogCommand command)
    {
        Debug.WriteLine(string.Format("Message \"{0}\" sent at {1}",
            command.LogMessage, command.Time));
    }
}


public class MessageCommand
{
    public string Message { get; set; }
}

public class MessageSender : ICommandHandler<MessageCommand>
{
    public void Execute(MessageCommand command)
    {
        Debug.WriteLine(command.Message);
    }
}

// message command handler decorator that logs about messages being sent
public class MessageLogger : ICommandHandler<MessageCommand>
{
    private ICommandHandler<MessageCommand> innerHandler;
    private ICommandHandler<LogCommand> logger;

    // notice these dependencies are two distinct closed generic types
    public MessageLogger(ICommandHandler<MessageCommand> innerHandler,
        ICommandHandler<LogCommand> logger)
    {
        this.innerHandler = innerHandler;
        this.logger = logger;
    }

    public void Execute(MessageCommand command)
    {
        innerHandler.Execute(command);

        var logCommand = new LogCommand
            {
                LogMessage = command.Message,
                Time = DateTime.Now
            };
        logger.Execute(logCommand);
    }
}

// this works as intended, but is tedious in a real-world app
ICommandHandler<MessageCommand> ResolveManually()
{
    ICommandHandler<MessageCommand> sender = new MessageSender();
    ICommandHandler<LogCommand> logger = new Logger();

    ICommandHandler<MessageCommand> loggerSender =
        new MessageLogger(sender, logger);

    return loggerSender;
}

// this is what I want to work - seems simple?
ICommandHandler<MessageCommand> ResolveWithSimpleInjector()
{
    var container = new Container();

    container.Register<ICommandHandler<LogCommand>, Logger>();

    container.Register<ICommandHandler<MessageCommand>, MessageSender>();

    // this next line throws the exception
    container.RegisterDecorator(typeof(ICommandHandler<MessageCommand>),
        typeof(MessageLogger));

    return container.GetInstance<ICommandHandler<MessageCommand>>();
}

void Main()
{
    //ICommandHandler<MessageCommand> sender = ResolveManually();
    ICommandHandler<MessageCommand> sender = ResolveWithSimpleInjector();

    var command = new MessageCommand { Message = "Hello World!" };
    sender.Execute(command);
}

我找不到任何有关这一情况的任何信息。这是一个bug,还是我失去了一些东西?

I could not find any information about this situation. Is this a bug, or am I missing something?

修改

我钓的反馈从SimpleInjector一个开发以找出是否有技术原因的限制,或者是一些被忽视了......除非有人能说服我没有在这个设计的逻辑缺陷和有一个很好的理由我不应该做的事情这样,这一切答案迄今还没有能够做到。我做AP preciate反馈。

I am fishing for feedback from a dev on SimpleInjector to find out if there is a technical reason for this limitation, or it is something that was overlooked... Unless someone can convince me there is a logical flaw in this design and there is a good reason I shouldn't do things this way, which none of the answers thus far have been able to do. I do appreciate the feedback.

在我看来,核心的问题是, RegisterDecorator()正在处理两种不同的封闭泛型类型为同一类型。有可能是一个技术性原因基于其内部运作,但也许不是?

It seems to me that the core issue is that RegisterDecorator() is treating two different closed generic types as the same type. There may be a technical reason for this based on its inner workings, but maybe not?

推荐答案

我不得不这样做在code基一番调查,看看发生了什么事情之一。你可以称这种现象简单的喷油器的实现一个小故障,但它是国际海事组织一个公平的权衡。简单的喷油器的装饰子系统是基于开放的泛型类型和开放式泛型装饰工作的想法。它在办理入住手续时装饰登记,看是否有装饰的构造函数只有一个decoratee。使用开放式泛型抽象到装饰必须应用此检查所做的是;你的情况 ICommandHandler&LT; T&GT; 。因为在这一点上只是普通的 ICommandHandler&LT; T&GT; 可用,两个构造参数匹配这个类型。

I had to do some investigation in the code base to see what was going one. You might call this a glitch in Simple Injector's implementation, but it's IMO a fair trade off. Simple Injector's decorator sub system is based around the idea of working with open generic types and open generic decorators. The check it does upon decorator registration is to see if a decorator's constructor has only one decoratee. This check is done using the open generic abstraction to which the decorator must be applied; in your case ICommandHandler<T>. Since at that point only the generic ICommandHandler<T> is available, two constructor parameters match this type.

有可能改善这些pre-条件检查,但其实这是很讨厌的,而这个功能的实用性是非常有限的。这是有限的,因为它是唯一的非普通装饰用。看看下面的装饰,例如:

It is possible to improve these pre-condition checks, but this is actually quite nasty, while the usefulness of this feature is very limited. It's limited because it's only useful for non-generic decorators. Take a look at the following decorator for instance:

public class GenericDecorator<TCommand> : ICommandHandler<TCommand> {
    public GenericDecorator(
        ICommandHandler<TCommand> decoratee,
        ICommandHandler<LoggingCommand> dependency)
    {
    }
}

这装饰是通用的,并允许您将其应用到任何装饰,这是更为有用。但是,会发生什么事,你解决了 ICommandHandler&LT; LoggingCommand&GT; ?这将导致循环依赖图和简单的喷油器将(显然)不能够创建一个图形,并会抛出异常。它必须抛出,因为装饰会在这种情况下有两个 ICommandHandler&LT; LoggingCommand&GT; 参数。第一个将是decoratee,将与注入你的日志,第二个将是一个正常的依赖,将与 GenericDecorator&LT注入; LoggingCommand&GT ; ,这是递归当然

This decorator is generic and allows you to apply it to any decorator, which is much more useful. But what happens when you resolve an ICommandHandler<LoggingCommand>? That would cause a cyclic dependency graph and Simple Injector will (obviously) not be able to create that graph and will throw an exception. It must throw, since the decorator will in that case have two ICommandHandler<LoggingCommand> arguments. The first will be the decoratee and will be injected with your Logger, and the second will be a normal dependency and will be injected with a GenericDecorator<LoggingCommand>, which is recursive of course.

所以,我认为这个问题是在你的设计。在对作曲指挥处理了其他的一些命令处理程序一般我的建议。该 ICommandHandler&LT; T&GT; 应该是躺在床上,定义了presentation层与业务层通信业务层的顶部的抽象。这不是一个机制,业务层在内部使用。如果你开始这样做,你的依赖配置变得非常复杂。下面是一个使用一个图形的一个例子 DeadlockRetryCommandHandlerDecorator&LT; T&GT; TransactionCommandHandlerDecorator&LT; T&GT;

So I would argue that the problem is in your design. In general I advice against composing command handlers out of other command handlers. The ICommandHandler<T> should be the abstraction that lies on top of your business layer that defines how the presentation layer communicates with the business layer. It's not a mechanism for the business layer to use internally. If you start doing this, your dependency configuration becomes very complicated. Here's an example of a graph that uses DeadlockRetryCommandHandlerDecorator<T> and a TransactionCommandHandlerDecorator<T>:

new DeadlockRetryCommandHandlerDecorator<MessageCommand>(
    new TransactionCommandHandlerDecorator<MessageCommand>(
        new MessageSender()))

在这种情况下, DeadlockRetryCommandHandlerDecorator&LT; T&GT; TransactionCommandHandlerDecorator&LT; T&GT; 被应用到 MessageSender 命令处理程序。但看看我们运用你的 MessageLogger 装饰以及发生了什么:

In this case the DeadlockRetryCommandHandlerDecorator<T> and a TransactionCommandHandlerDecorator<T> are applied to the MessageSender command handler. But look what happens we apply your MessageLogger decorator as well:

new DeadlockRetryCommandHandlerDecorator<MessageCommand>(
    new TransactionCommandHandlerDecorator<MessageCommand>(
        new MessageLogger(
            new MessageSender(),
            new DeadlockRetryCommandHandlerDecorator<MessageLogger>(
                new TransactionCommandHandlerDecorator<MessageLogger>(
                    new Logger())))))

请注意还有第二个 DeadlockRetryCommandHandlerDecorator&LT; T&GT; 和第二 TransactionCommandHandlerDecorator&LT; T&GT; 中的对象图。这是什么意思有一个交易的交易,并有一个嵌套的死锁重试(在事务内)。这可以在你的应用程序造成严重的可靠性问题(因为数据库死锁会导致您的操作在一个事务中-less连接继续)。

Notice how there's a second DeadlockRetryCommandHandlerDecorator<T> and a second TransactionCommandHandlerDecorator<T> in the object graph. What does it mean to have a transaction in a transaction and have a nested deadlock retry (within a transaction). This can cause serious reliability problems in your application (since a database deadlock will cause your operation to continue in a transaction-less connection).

尽管可以创建在这样的方式的装饰,它们能够检测到它们嵌套以使它们它们嵌套在壳体正确地工作,这使得实现它们更难和更脆弱。国际海事组织这是你的时间赤身。

Although it is possible to create your decorators in such way that they are able to detect that they are nested to make them work correctly in case they're nested, this makes implementing them much harder and much more fragile. IMO that's a wast of your time.

。你的情况,这个问题可以很容易地固定通过让它使用某种形式的 ILogger 界面改变你的装饰:

So instead of allowing command handlers to be nested, let command handlers and command handler decorators depend upon other abstractions. In your case, the problem can be easily fixed by changing your decorator by letting it use an ILogger interface of some sort:

public class MessageLogger : ICommandHandler<MessageCommand> {
    private ICommandHandler<MessageCommand> innerHandler;
    private ILogger logger;

    public MessageLogger(
        ICommandHandler<MessageCommand> innerHandler, ILogger logger) {
        this.innerHandler = innerHandler;
        this.logger = logger;
    }

    public void Execute(MessageCommand command) {
        innerHandler.Execute(command);

        logger.Log(command.Message);
    }
}

您仍然可以有一个 ICommandHandler&LT; LogCommand&GT; 的情况下实施的presentation层需要直接登录,但在这种情况下,该实现可以简单地依赖于 ILogger 以及

You can still have an ICommandHandler<LogCommand> implementation in case the presentation layer needs to log directly, but in that case that implementation can simply depend on that ILogger as well:

public class LogCommandHandler : ICommandHandler<LogCommand> {
    private ILogger logger;

    public LogCommandHandler(ILogger logger) {
        this.logger = logger;
    }

    public void Execute(LogCommand command) {
        logger(string.Format("Message \"{0}\" sent at {1}",
            command.LogMessage, DateTime.Now));
    }
}

这篇关于简单的喷油器 - 注册装饰用同一个通用类型的另一种依赖的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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