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

本文关键字:应用 泛型 有条件 泛型类型 约束 autofacc | 更新日期: 2023-09-27 18:02:21

我有一个基于查询/处理程序架构的应用程序。我有以下接口:

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封装。

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

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

如果我继承了一个有装饰链的代码库,我希望看到的是:

// 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——这是故意的。我认为Decorator模式是"最有害的",因为很多时候它颠覆了Liskov替换原则。例如,如果你在任何地方都使用IQueryHandler,那么一个配置错误的DI容器可能会忽略一个授权装饰器——类型系统不会抱怨,但你的应用肯定是坏了。出于这个原因,我喜欢将"调用站点抽象"与"实现站点抽象"分开(参见我的另一个答案中的事件处理程序与事件处理程序),并使两者之间的任何内容尽可能显式。