Как украсить зависимость, но только внутри дочернего контейнера - PullRequest
0 голосов
/ 01 ноября 2019

Проблема

Допустим, у меня есть сторонний код, способный зарегистрировать 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).

...