用相同泛型的另一个依赖项注册装饰器 [英] Register decorator with another dependency of same generic type

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

问题描述

我想我偶然发现了Simple Injector的 RegisterDecorator()的一个怪癖。即使在最新的2.5.0中也会发生。我遇到一种情况,我想装饰一个封闭的泛型类型,例如 ICommandHandler< MessageCommand> ,并且装饰器需要(通过构造函数注入)一个类型为<的内部处理程序code> ICommandHandler< MessageCommand> ,但是还有另一种类型的处理程序,例如 ICommandHandler< LogCommand> 。即使这些命令处理程序类型是不同的,当我在这样的装饰器类型上调用 RegisterDecorator 时,SimpleInjector仍会感到困惑并引发异常:

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< MessageCommand> (或 Func< ICommandHandler< MessageCommand>> )-即正在装饰的实例的类型。参数类型 ICommandHandler< MessageCommand> 在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< MessageCommand> 参数。

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

我找不到有关此情况的任何信息。这是一个错误,还是我缺少什么?

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

编辑

我希望从SimpleInjector上的开发人员那里获得反馈,以了解是否存在此限制的技术原因,或者被忽略的原因...除非有人可以说服我这种设计存在逻辑缺陷,我不应该以这种方式做事的原因,到目前为止,没有一个答案可以做到。我确实感谢您的反馈。

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?

推荐答案

我不得不根据以下方面进行调查:代码库,看看发生了什么。您可能会在Simple Injector的实现中将此称为小故障,但这是IMO的权衡取舍。 Simple Injector的装饰器子系统基于使用开放通用类型和开放通用装饰器的思想。在装饰器注册时进行的检查是查看装饰器的构造函数是否只有一个被装饰者。使用必须将装饰器应用到的开放通用抽象来完成此检查;在您的情况下 ICommandHandler< T> 。由于那时只有通用 ICommandHandler< T> 可用,所以两个构造函数参数与此类型匹配。

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.

可以改善这些前提条件检查,但这实际上很讨厌,而此功能的用途非常有限。由于它仅对非通用装饰器有用,因此受到限制。例如,看一下以下装饰器:

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< LoggingCommand> 时会发生什么?这将导致循环依赖图,并且简单注入器(显然)将无法创建该图,并会引发异常。它必须抛出,因为在这种情况下装饰器将具有两个 ICommandHandler< LoggingCommand> 参数。第一个将是decoratee,将被注入您的 Logger ,第二个将是一个普通的依赖项,并将被注入一个 GenericDecorator< LoggingCommand> ; ,这当然是递归的。

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< T> 应该是位于您的业务层之上的抽象层,用于定义表示层与业务层的通信方式。这不是业务层内部使用的机制。如果开始执行此操作,则依赖项配置将变得非常复杂。这是一个使用 DeadlockRetryCommandHandlerDecorator< T> TransactionCommandHandlerDecorator< T> 的图形的示例:

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< T> TransactionCommandHandlerDecorator< T> 应用于 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< T> 和对象图中的第二个 TransactionCommandHandlerDecorator< T> 。在事务中具有事务并嵌套嵌套重试(在事务内)是什么意思。这可能会在您的应用程序中引起严重的可靠性问题(因为数据库死锁会导致您的操作在无事务连接中继续进行 )。

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

尽管可以用这样的方式创建装饰器,使它们能够检测到它们被嵌套,以防嵌套时它们能够正常工作,这使得实施它们变得更加困难和脆弱。 IMO,这会浪费您的时间。

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 waste 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< LogCommand> 实现,但在这种情况下,实现也可以仅依赖于 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天全站免登陆