Применение универсальных декораторов условно в Autofac на основе ограничений универсального типа
У меня есть приложение с архитектурой на основе запросов / обработчиков. У меня есть следующий интерфейс:
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?
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
- это специально. Я пришел к выводу, что шаблон Decorator "в основном вреден", поскольку он часто подрывает принцип подстановки Лискова. Например, если вы используете IQueryHandler
везде неправильно сконфигурированный DI-контейнер может опустить декоратор авторизации - система типов не будет жаловаться, но ваше приложение определенно сломано. По этой причине я предпочитаю отделить "абстракцию сайта вызова" от "абстракции сайта реализации" (см. "Обработчик событий против Event Raiser" в другом из моих ответов) и сделать что-то промежуточное между ними как можно более явным.