适合 Unity 的抽象工厂
Abstract Factory fit into Unity
我们正在构建一个与其他系统有多个集成接触点的应用程序。我们有效地使用 Unity 来满足我们所有的依赖注入需求。整个业务层是用接口驱动的方法构建的,在应用程序 bootstrap 期间在外部组合根中注入实际实现。
我们希望以优雅的方式处理集成层。业务 classes 和存储库依赖于 IIntegrationController<A, B>
接口。几个 IIntegrationController<A, B>
实现一起表示在后台与一个目标系统集成 - 形成一个集成层。目前,我们在一开始就连接了构图根中的所有内容,一个镜头。此接口的使用者也预先注册了适当的 InjectionConstrutor
和 ResolvedParameter
。大多数类型都使用 PerResolveLifetime
进行操作,并且使用 IIntegrationController
的业务 class 也针对每个请求上下文分别解析。
参考下面的代码。
// 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>
系列。当业务 class 被解析时,我们希望它根据上下文中可用的请求参数注入正确版本的 IIntegrationController<A, B>
。
- 基于 "named" 注册的解决方案将不可扩展,原因有二(必须切换整个集成系列 classes,并且需要笨重的名称注册和条件解析在代码中很难维护)。
- 即使有 chain/hierarchy 个解决方案,该解决方案也应该有效,即
IIntegrationController
的直接消费者也通过 Unity 解决,因为它被注入另一个 [=52] =]动态。
- 我们在解决过程中尝试了
DependencyOverride
和 ResolveOverride
class,但这需要覆盖整套 Family-2 IIntegrationController
解决方案,而不是只是能够切换整个图层。
- 我们了解到,不是直接将 IIntegrationController 注入到业务中 class,而是可能需要注入一个 AbstractFactory,但我们无法使其正常工作,并且不确定注册和解析的位置会发生。如果业务 class 与 AbstractFactory 挂钩,首先我必须为每个解析挂钩正确的工厂,
- 这是否需要覆盖
InjectionFactory
? This link 提出了一种方法,但我们无法使其顺利运行。
您的设计的优点在于您已经有了正确的抽象。您使用通用抽象,因此只需在您已经 SOLID 的设计之上应用正确的模式即可解决问题。
换句话说,使用代理:
// 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;
}
}
有了这个 class 你整个配置可以减少到大约这个:
container.RegisterType<IUserContext, AspNetUserContext>();
container.RegisterType(
typeof(IIntegrationController<,>),
typeof(IntegrationControllerDispatcher<,>));
container.RegisterType(typeof(Repository<>), typeof(Repository<>));
注意以下几点:
- 注意使用注册进行开放通用映射。您不必一一注册所有封闭版本。一行代码即可搞定。
- 另请注意,不同系列的类型并不相同
挂号的。 Unity 可以自动解决它们,因为我们的
IntegrationControllerDispatcher
直接依赖于他们。这个
class是一段基础设施逻辑,应该放在里面
你的组合根。
- 请注意,使用特定系列实现的决定不是在构建对象图期间做出的;它是在运行时生成的,因为决定这个的值是运行时值。试图在构建对象图时确定这一点,只会使事情复杂化,并使验证对象图变得更加困难。
- 此外,此运行时值在函数调用之后被抽象化并置于抽象之后(
IUserContext.IsInFamily
在这种情况下,但这当然只是一个示例)。
我们正在构建一个与其他系统有多个集成接触点的应用程序。我们有效地使用 Unity 来满足我们所有的依赖注入需求。整个业务层是用接口驱动的方法构建的,在应用程序 bootstrap 期间在外部组合根中注入实际实现。
我们希望以优雅的方式处理集成层。业务 classes 和存储库依赖于 IIntegrationController<A, B>
接口。几个 IIntegrationController<A, B>
实现一起表示在后台与一个目标系统集成 - 形成一个集成层。目前,我们在一开始就连接了构图根中的所有内容,一个镜头。此接口的使用者也预先注册了适当的 InjectionConstrutor
和 ResolvedParameter
。大多数类型都使用 PerResolveLifetime
进行操作,并且使用 IIntegrationController
的业务 class 也针对每个请求上下文分别解析。
参考下面的代码。
// 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>
系列。当业务 class 被解析时,我们希望它根据上下文中可用的请求参数注入正确版本的 IIntegrationController<A, B>
。
- 基于 "named" 注册的解决方案将不可扩展,原因有二(必须切换整个集成系列 classes,并且需要笨重的名称注册和条件解析在代码中很难维护)。
- 即使有 chain/hierarchy 个解决方案,该解决方案也应该有效,即
IIntegrationController
的直接消费者也通过 Unity 解决,因为它被注入另一个 [=52] =]动态。 - 我们在解决过程中尝试了
DependencyOverride
和ResolveOverride
class,但这需要覆盖整套 Family-2IIntegrationController
解决方案,而不是只是能够切换整个图层。 - 我们了解到,不是直接将 IIntegrationController 注入到业务中 class,而是可能需要注入一个 AbstractFactory,但我们无法使其正常工作,并且不确定注册和解析的位置会发生。如果业务 class 与 AbstractFactory 挂钩,首先我必须为每个解析挂钩正确的工厂,
- 这是否需要覆盖
InjectionFactory
? This link 提出了一种方法,但我们无法使其顺利运行。
您的设计的优点在于您已经有了正确的抽象。您使用通用抽象,因此只需在您已经 SOLID 的设计之上应用正确的模式即可解决问题。
换句话说,使用代理:
// 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;
}
}
有了这个 class 你整个配置可以减少到大约这个:
container.RegisterType<IUserContext, AspNetUserContext>();
container.RegisterType(
typeof(IIntegrationController<,>),
typeof(IntegrationControllerDispatcher<,>));
container.RegisterType(typeof(Repository<>), typeof(Repository<>));
注意以下几点:
- 注意使用注册进行开放通用映射。您不必一一注册所有封闭版本。一行代码即可搞定。
- 另请注意,不同系列的类型并不相同
挂号的。 Unity 可以自动解决它们,因为我们的
IntegrationControllerDispatcher
直接依赖于他们。这个 class是一段基础设施逻辑,应该放在里面 你的组合根。 - 请注意,使用特定系列实现的决定不是在构建对象图期间做出的;它是在运行时生成的,因为决定这个的值是运行时值。试图在构建对象图时确定这一点,只会使事情复杂化,并使验证对象图变得更加困难。
- 此外,此运行时值在函数调用之后被抽象化并置于抽象之后(
IUserContext.IsInFamily
在这种情况下,但这当然只是一个示例)。