Абстрактная фабрика вписывается в Unity

Мы создаем приложение, которое имеет точки соприкосновения числа с другими системами. Мы эффективно используем Unity для всех наших потребностей внедрения зависимостей. Весь бизнес-уровень был построен с использованием подхода, основанного на интерфейсе, с фактической реализацией, внедряемой во внешний корень композиции во время начальной загрузки приложения.

Мы хотели обработать уровень интеграции элегантно. Бизнес-классы и репозитории зависят от IIntegrationController<A, B> интерфейсы. Несколько IIntegrationController<A, B> Совместные реализации представляют интеграцию с одной целевой системой в фоновом режиме - формирование уровня интеграции. В настоящее время мы подключаем все в корне композиции, одним выстрелом, в начале. Потребители этого интерфейса также зарегистрированы с соответствующим InjectionConstrutor а также ResolvedParameter впереди Большинство типов работают с PerResolveLifetime и бизнес-классы, потребляющие IIntegrationController также разрешаются для каждого контекста запроса отдельно.

Смотрите код ниже.

        // IIntegrationController Family 1
        // Currently the default registration for IIntegrationController types injected into the business classes
        container.RegisterType<IIntegrationController<A, B>, Family1-IntegrationController<A, B>>();
        container.RegisterType<IIntegrationController<C, D>, Family1-IntegrationController<C, D>>();

        // IIntegrationController Family 2 (currently not registered)
        // We want to be able to register this, or manage this set of mapping registrations separately from Family 1,
        // and be able to hook these up dynamically instead of Family-1 on a per-resolve basis
        container.RegisterType<IIntegrationController<A, B>, Family2-IntegrationController<A, B>>();
        container.RegisterType<IIntegrationController<C, D>, Family2-IntegrationController<C, D>>();

        // Repository/Business Class that consume IIntegrationControllers.
        // There is a whole family of IIntegrationController classes being hooked in, 
        // and there are multiple implementations for the family (as shown above). A typical AbstractFactory scenario.
        container.RegisterType(typeof(Repository<Z>), new PerResolveLifetimeManager(),
            new InjectionConstructor(
                new ResolvedParameter<IIntegrationController<A, B>>(), 
                new ResolvedParameter<IIntegrationController<C, D>>())
        );

Постановка задачи:

Мы хотим возможность поменять всю семью IIntegrationController<A, B> во время выполнения. Когда бизнес-класс решается, мы хотим, чтобы ему вводили правильную версию IIntegrationController<A, B> на основе параметра запроса, доступного в контексте.

  • "Названное" решение, основанное на регистрации, не будет масштабируемым по двум причинам (необходимо переключить все семейство классов интеграции, и для этого потребуются неуклюжие регистрации имен и условное разрешение в коде, что усложнит поддержку).
  • Решение должно работать, даже когда существует цепочка / иерархия решений, т. Е. Прямой потребитель IIntegrationController также разрешается через Unity, поскольку динамически внедряется в другой класс.
  • Мы попробовали DependencyOverride а также ResolveOverride класс во время разрешения, но для этого потребуется весь набор Family-2 IIntegrationController разрешения, которые нужно переопределить, вместо того, чтобы просто переключать весь слой.
  • Мы понимаем, что вместо внедрения IIntegrationController непосредственно в бизнес-класс может потребоваться внедрение AbstractFactory, но мы не можем заставить это работать и не уверены, где произойдет регистрация и разрешение. Если бизнес-класс подключен к AbstractFactory, сначала мне нужно подключить правильную фабричную настройку,
  • Требует ли это переопределения InjectionFactory? Эта ссылка предлагает подход, но мы не смогли заставить его работать гладко.

1 ответ

Решение

Что приятно в вашем дизайне, так это то, что у вас уже есть правильные абстракции. Вы используете общие абстракции, поэтому проблему можно решить, просто применив правильные шаблоны поверх вашего уже твердого проекта.

Другими словами, используйте прокси:

// This class should be considered part of your composition root.
internal class IntegrationControllerDispatcher<TRequest, TResult> 
    : IIntegrationController<TRequest, TResult>
{
    private readonly IUserContext userContext;
    private readonly Family1_IntegrationController<A, B> family1Controller;
    private readonly Family2_IntegrationController<A, B> family2Controller;

    public IntegrationControllerDispatcher(
        IUserContext userContext,
        Family1_IntegrationController<A, B> family1Controller,
        Family2_IntegrationController<A, B> family2Controller) {
        this.userContext = userContext;
        this.family1Controller = family1Controller;
        this.family2Controller = family2Controller;
    }

    public TResult Handle(TRequest request) {
        return this.GetController().Handle(request);
    }

    private IIntegrationController<TRequest, TResult> GetController() {
        return this.userContext.IsInFamily("family1"))
            ? this.family1Controller
            : this.family2Controller;
    }
}

С этим классом вся ваша конфигурация может быть уменьшена примерно до этого:

container.RegisterType<IUserContext, AspNetUserContext>();

container.RegisterType( 
    typeof(IIntegrationController<,>), 
    typeof(IntegrationControllerDispatcher<,>));

container.RegisterType(typeof(Repository<>), typeof(Repository<>));

Обратите внимание на следующее:

  • Обратите внимание на использование регистраций, которые выполняют открытое универсальное сопоставление. Вам не нужно регистрировать ВСЕ ваши закрытые версии одну за другой. Вы можете сделать это с помощью одной строки кода.
  • Также обратите внимание, что типы для разных семейств не зарегистрированы. Единство может решить их автоматически, потому что нашиIntegrationControllerDispatcher зависит от них напрямую. Этот класс является частью логики инфраструктуры и должен быть помещен в ваш корень композиции.
  • Обратите внимание, что решение использовать конкретную реализацию семейства не принимается во время построения графа объектов; Это сделано во время выполнения, потому что значение, которое определяет это, является значением времени выполнения. Попытка определить это во время построения графа объектов только усложнит ситуацию и усложнит проверку ваших графов объектов.
  • Кроме того, это значение времени выполнения абстрагируется после вызова функции и помещается за абстракцией (IUserContext.IsInFamily в данном случае, но это только пример, конечно).
Другие вопросы по тегам