我们正在构建一个与其他系统具有多个集成接触点的应用程序。我们正在有效地使用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
? 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;
}
}
使用此类,您可以将整个配置简化为:
container.RegisterType<IUserContext, AspNetUserContext>();
container.RegisterType(
typeof(IIntegrationController<,>),
typeof(IntegrationControllerDispatcher<,>));
container.RegisterType(typeof(Repository<>), typeof(Repository<>));
请注意以下几点:
请注意使用进行开放通用映射的注册。您不必一一注册所有封闭版本。您可以使用一行代码来做到这一点。
另请注意,不同家庭的类型不是
注册。 Unity可以自动解决它们,因为我们
IntegrationControllerDispatcher
直接取决于它们。这个类是基础架构逻辑的一部分,应放置在内部
您的成分根。
请注意,在构建对象图的过程中不会做出使用特定系列实现的决定。它是在运行时生成的,因为确定它的值是运行时值。尝试在构建对象图时确定这一点,只会使事情复杂化,并使验证对象图更加困难。
此外,此运行时值在函数调用之后抽象,并放在抽象之后(在本例中为
IUserContext.IsInFamily
,但这当然只是示例)。