基于泛型类型约束,在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中使用通用装饰器来实现这一点?
如果我继承了一个有装饰链的代码库,我希望看到的是:
// 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容器可能会忽略一个授权装饰器——类型系统不会抱怨,但你的应用肯定是坏了。出于这个原因,我喜欢将"调用站点抽象"与"实现站点抽象"分开(参见我的另一个答案中的事件处理程序与事件处理程序),并使两者之间的任何内容尽可能显式。