Проблема
Допустим, у меня есть сторонний код, способный зарегистрировать IService
в StructureMap контейнере зависимостей. Третья сторона владеет как интерфейсом, так и реализацией.
Цель - предоставить механизм для изменения поведения IService
по требованию. То есть я хочу иметь возможность динамически выбирать между базовой и модифицированной версией в течение всего срока службы моего приложения.
Чтобы сделать вещи сложнее, на самом деле я никогда не вытаскиваю IService
непосредственно из контейнера, а скорее естьмножество уже существующих сервисов, которые потребляют IService
в качестве зависимости, и еще больше сервисов, которые косвенно зависят от IService
.
Обратите внимание, что я не хочу полностью заменять функциональность оригинального IService
, но немного измените значения, которые он возвращает.
Предложенное решение
Кажется очевидным выбор использования шаблона декоратора для изменения поведения оригинала IService
. Поскольку я хотел бы исключить логику выбора из существующих потребительских сервисов и во избежание рефакторинга существующего кода, я не могу представить декорированную версию IService
как другой тип, такой как IDecoratedService
.
Из-за проблем с производительностью (некоторые сервисы используют кэшированные данные), я бы хотел избежать создания двух независимых контейнеров (что также означало бы кэширование данных дважды). Поэтому кажется, что использование дочернего контейнера - это идеальное решение (вложенный контейнер тоже подойдет).
Пример
Допустим, это сторонний код:
public interface IService { string Text { get; } }
public class ThirdPartyRegistry : Registry
{
private class Service : IService { public string Text => "Service"; }
public ThirdPartyRegistry()
{
For<IService>().Use<Service>();
}
}
Обратите внимание, что у меня нет доступа к классу, реализующему службу.
Чего я хочу добиться, так это создать дочерний контейнер, который при запросе IService
будет возвращать декорированную версиюсервис, предоставляемый третьей стороной.
Вот пример реестра, который должен делать это:
public class MyRegistry : Registry
{
private class ServiceDecorator : IService
{
private readonly IService _service;
public ServiceDecorator(IService service) { _service = service; }
public string Text => _service.Text + ", but decorated";
}
public MyRegistry()
{
// What do I do here?
}
}
Требование заключается в том, что когда мы запрашиваем у родительского контейнера IService
, мыполучить исходную реализацию, но если мы спросим дочерний контейнер, мы получим декорированную версию.
Вот простой тестовый прибор (с использованием NUnit ), который отображает требования:
public class Tests
{
private IContainer _parentContainer;
private IContainer _childContainer;
[SetUp]
public void SetUp()
{
_parentContainer = new Container(x => x.AddRegistry<ThirdPartyRegistry>());
_childContainer = _parentContainer.CreateChildContainer();
_childContainer.Configure(x => x.AddRegistry<MyRegistry>());
}
[Test]
public void Parent_container_should_return_undecorated_service()
{
Assert.AreEqual(
"Service",
_parentContainer.GetInstance<IService>().Text);
}
[Test]
public void Child_container_should_return_decorated_service()
{
Assert.AreEqual(
"Service, but decorated",
_childContainer.GetInstance<IService>().Text);
}
}
Неудачные попытки
Я пытался достичь своей цели с помощью этих методов (в конструкторе MyRegistry
), но безуспешно:
For<IService>().DecorateAllWith<ServiceDecorator>()
- этопроходит мимотест cond, но не прошел первый. То есть, он также переопределяет поведение IService
в родительском контейнере, чего не должно быть.
For<IService>().Use<ServiceDecorator>()
- второй тест завершается с сообщением, говорящим что-то вроде«Обнаружена двунаправленная зависимость!», Что неудивительно.
Forward<ServiceDecorator, IService>()
- то же, что и выше.
Ключевая проблемаПохоже, что это говорит дочернему контейнеру использовать IService
экземпляр, зарегистрированный сторонним реестром, для создания ServiceDecorator
.
Обратите внимание, что я был бы совершенно рад изменить код, изображенный в тестовом классе. ,В частности, как настроены родительский и дочерний контейнеры (содержимое метода SetUp
).