Применение универсальных декораторов условно в 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" в другом из моих ответов) и сделать что-то промежуточное между ними как можно более явным.

Другие вопросы по тегам