AutoFac Interception, как отличить внутреннее от внешнего

Поэтому я пытаюсь реализовать концепцию перехвата, используя автофак. Я не делаю ничего особенного, например, реализую динамический перехват, каждый класс, в котором у меня есть конкретный код.

Мой код (не мой настоящий код, я нашел его в Интернете, но он демонстрирует мою проблему

public class DefaultProductService : IProductService
{       
    public Product GetProduct(int productId)
    {
        return new Product();
    }
}

public class CachedProductService : IProductService
{
    private readonly IProductService _innerProductService;
    private readonly ICacheStorage _cacheStorage;

    public CachedProductService(IProductService innerProductService, ICacheStorage cacheStorage)
    {
        if (innerProductService == null) throw new ArgumentNullException("ProductService");
        if (cacheStorage == null) throw new ArgumentNullException("CacheStorage");
        _cacheStorage = cacheStorage;
        _innerProductService = innerProductService;
    }

    public Product GetProduct(int productId)
    {
        string key = "Product|" + productId;
        Product p = _cacheStorage.Retrieve<Product>(key);
        if (p == null)
        {
            p = _innerProductService.GetProduct(productId);
            _cacheStorage.Store(key, p);
        }

        return p;
    }
}

public class ProductManager : IProductManager
{
    private readonly IProductService _productService;

    public ProductManager(IProductService productService)
    {
        _productService = productService;
    }
}

Моя проблема заключается в том, что я хочу, чтобы мой ProductManager получил "CachedProductService" для IProductService, и я хочу, чтобы мой CachedProductService получил "DefaultProductService" для IProductService.

Я знаю несколько решений, но ни одно из них не кажется абсолютно правильным. Как правильно это сделать?

Спасибо! Майкл

3 ответа

То, что вы пытаетесь сделать, кажется не концепцией перехвата, а концепцией декоратора.

В объектно-ориентированном программировании шаблон декоратора является шаблоном проектирования, который позволяет добавлять поведение к отдельному объекту, статически или динамически, без влияния на поведение других объектов того же класса.
из шаблона Decorator (Википедия)

Выкройка декоратора

Autofac имеет встроенную поддержку декоратора с использованием именованной регистрации.

Во-первых, вы должны объявить ваш базовый компонент и декораторы как именованную регистрацию:

builder.RegisterType<DefaultProductService>().Named<IProductService>("base"); 
builder.RegisterType<CacheProductServiceAdapter>().Named<IProductService>("cache"); 

Тогда вы можете зарегистрировать свой декоратор

builder.RegisterDecorator((c, inner) => c.ResolveNamed<IProductService>("cache", TypedParameter.From(inner)), fromKey : "base")
       .As<IProductService>(); 

Вы также можете иметь более одного адаптера:

builder.RegisterType<DefaultProductService>().Named<IProductService>("base"); 
builder.RegisterType<CacheProductServiceAdapter>().Named<IProductService>("cache"); 
builder.RegisterType<LoggingProductServiceAdapter>().Named<IProductService>("logging"); 

builder.RegisterDecorator((c, inner) => c.ResolveNamed<IProductService>("cache", TypedParameter.From(inner)), fromKey : "base", toKey:"cached"); 
builder.RegisterDecorator((c, inner) => c.ResolveNamed<IProductService>("logging", TypedParameter.From(inner)), fromKey : "cached")
       .As<IProductService>(); 

См. Адаптеры и декораторы из документации Autofac для получения дополнительной информации.

Вы можете сделать это, используя именованные зависимости.
Вместо того, чтобы сказать, что реализация IProductService является DefaultProductServiceу вас будет две реализации, отличающиеся своими именами, например:

builder.Register<DefaultProductService>().Named<IProductService>("DefaultProductService");
builder.Register<CachedProductService>().Named<IProductService>("CachedProductService");

В документации описано несколько способов указать, какую реализацию вы хотите внедрить в данный класс. Это выглядит как самый простой и самый явный:

public class ProductManager : IProductManager
{
    private readonly IProductService _productService;

    public ProductManager([WithKey("CachedProductService")]IProductService productService)
    {
        _productService = productService;
    }
}

Если честно, мне это не нравится, потому что это вызывает зависимость класса от контейнера. Это не позволяет вам настроить это полностью отдельно от самих классов. С Windsor вы будете делать это при регистрации зависимостей с контейнером, сообщая ему, какие именованные зависимости использовать.

Но это должно работать.

Основываясь на предложении Скотта, я думаю, что я нашел способ без необходимости вводить зависимость от контейнера в моем классе ProductManager (я действительно ненавидел это делать!). Я могу просто создать экземпляр CachedProductService и сказать ему, какой ProductService использовать

builder.RegisterType(typeof(ProductService)).Named<IProductService>("DefaultProductService");
builder.Register(c => new CachedProductService(c.ResolveKeyed<IProductService>("DefaultProductService"))).As<IProductService>();
Другие вопросы по тегам