Абстрактная фабрика вписывается в 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-2IIntegrationController
разрешения, которые нужно переопределить, вместо того, чтобы просто переключать весь слой. - Мы понимаем, что вместо внедрения 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
в данном случае, но это только пример, конечно).