【问题标题】:Applying generic decorators conditionally in Autofac based on generic type constraints基于泛型类型约束在 Autofac 中有条件地应用泛型装饰器
【发布时间】:2014-05-08 19:16:58
【问题描述】:

我有一个带有query/handler based architecture 的应用程序。我有如下界面:

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

这个接口有很多非通用的实现。这些实现由通用装饰器包装,用于日志记录、分析、授权等。但有时我想根据装饰器的通用类型约束有条件地应用通用装饰器。以这个缓存装饰器为例,它只能应用于返回 ReadOnlyCollection&lt;T&gt; 的查询(仅仅是因为缓存任何可变的集合没有多大意义):

public class CachingQueryHandlerDecorator<TQuery, TResult> 
    : IQueryHandler<TQuery, ReadOnlyCollection<TResult>>
    where TQuery : IQuery<ReadOnlyCollection<TResult>>
{
    private readonly IQueryHandler<TQuery, ReadOnlyCollection<TResult>> decoratee;
    private readonly IQueryCache cache;

    public CachingQueryHandlerDecorator(
        IQueryHandler<TQuery, ReadOnlyCollection<TResult>> decoratee,
        IQueryCache cache)
    {
        this.decoratee = decoratee;
        this.cache = cache;
    }

    public ReadOnlyCollection<TResult> Handle(TQuery query)
    {
        ReadOnlyCollection<TResult> result;

        if (!this.cache.TryGetResult(query, out result))
        {
            this.cache.Store(query, result = this.decoratee.Handle(query));
        }

        return result;
    }
}

更棘手的是,这些条件装饰器可能位于装饰器链中的任何位置。他们往往是中间的装饰者之一。例如,这个 CachingQueryHandlerDecorator 包装了一个非条件 ProfilingQueryHandlerDecorator 并且应该被一个条件 SecurityQueryHandlerDecorator 包装。

我发现this answer 是指有条件地应用非泛型装饰器;不是关于基于泛型类型约束有条件地应用泛型装饰器。我们如何使用 Autofac 中的通用装饰器来实现这一点?

【问题讨论】:

  • @JimBolla:这不是重复的 IMO。尽管两者都是关于有条件地处理泛型装饰器,但这个问题明确地与泛型类型约束有关,这很可能需要不同的方式来处理它们。
  • 答案将完全相同...实现一个类似于原始问题中推荐的 IRegistrationSource。唯一的区别在于if 语句。哎呀,你可以让这个问题的实现和另一个共享一个通用的抽象基类。
  • @JimBolla:让我们等待尼克对此的看法。他答应我这个周末回答这些问题。

标签: c# generics dependency-injection ioc-container autofac


【解决方案1】:

如果我继承了一个包含装饰器链的代码库,这就是我希望看到的:

// Think of this as the "master decorator" - all calling code comes through here.
class QueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    private readonly IComponentContext context;

    public QueryHandler(IComponentContext context)
    {
        this.context = context;
    }

    public TResult Handle(TQuery query)
    {
        var handler = context.Resolve<IQueryHandler<TQuery, TResult>>();

        if (typeof(TResult).IsClosedTypeOf(typeof(ReadOnlyCollection<>)))
        {
            // manual decoration:
            handler = new CachingQueryHandlerDecorator<TQuery, TResult>(handler);

            // or, container-assisted decoration:
            var decoratorFactory = context.Resolve<Func<IQueryHandler<TQuery, TResult>, CachingQueryHandlerDecorator<TQuery, TResult>>>();
            handler = decoratorFactory(handler);
        }

        if (NeedsAuthorization(query)) { ... }

        return handler.Handle(query);
    }
}

由于装饰顺序很重要,我希望能够看到它并轻松更改它并在需要时逐步执行它。即使我是 DI 新手,我也可以维护这段代码。

但是,如果您有一堆乱七八糟的键和回调以及容器驱动的魔法,我将很难维护它。仅仅因为您可以将容器用于某事并不意味着您应该

最后,请注意我的 QueryHandler 类没有实现 IQueryHandler - 这是故意的。我开始认为装饰器模式是“最有害的”,因为它在很多时候颠覆了 Liskov 替换原则。例如,如果你在任何地方都使用IQueryHandler,那么配置错误的 DI 容器可能会省略 Authorization 装饰器——类型系统不会抱怨,但你的应用程序肯定是坏了。出于这个原因,我喜欢将“调用站点抽象”与“实现站点抽象”分开(参见another of my answers 上的事件处理程序与事件引发器),并尽可能明确地说明两者之间的任何内容。

【讨论】:

  • 感谢您花时间回答我的问题。我不同意你的“装饰模式是有害的”声明(尽管任何模式都可以很容易地被滥用,这是肯定的)。但是,您当前方法的问题是它无法编译。这是由泛型类型约束引起的。这是可以解决的,但会变得非常难看。正如你已经说过的,我们希望有一个可维护的代码库。但也许问题是我被 Simple Injector 宠坏了 :-(.
  • @Steven - 您提到这个问题是可以解决的,但您没有提供解决方案(针对您的任何一个问题)。看起来这应该是在 Autofac 上添加一个扩展方法来隐藏这背后令人讨厌的细节,但我想知道你最终想出了什么。
  • @NightOwl888 我们能够通过将类型检查和强制转换移动到我们的装饰器代码中来从装饰器中移除泛型类型约束。这意味着总是应用装饰器,装饰器内的if 语句将决定是否应用装饰逻辑。这很容易出错并且非常丑陋,但在我们迁移到 Simple Injector 之前一直有效。我们从来没有投资于做一个好的扩展,因为我知道把它做好是多么的困难。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多