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>();