基于泛型类型约束在Autofac中有条件地应用泛型装饰器

时间:2014-05-08 19:16:58

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

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

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

此接口有许多非通用实现。这些实现由通用装饰器包装,用于记录,分析,授权等。有时我想根据装饰器的泛型类型约束有条件地应用通用装饰器。举个例子,这个缓存装饰器只能应用于返回ReadOnlyCollection<T>的查询(只是因为缓存任何可变的集合没有多大意义):

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中的通用装饰器实现这一目标?

1 个答案:

答案 0 :(得分:-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 - 这是故意的。我认为装饰者模式“主要是有害的”,因为很多时候它颠覆了利斯科夫的替代原则。例如,如果你在任何地方使用IQueryHandler,那么错误配置的DI容器可能会省略授权装饰器 - 类型系统不会抱怨,但你的应用程序肯定会被破坏。出于这个原因,我喜欢将“调用站点抽象”与“实现站点抽象”分开(请参阅another of my answers上的事件处理程序与事件提升器),并尽可能明确地使用任何内容。