【发布时间】:2014-05-07 05:46:49
【问题描述】:
关于这个话题有大量的讨论,但每个人似乎都错过了一个明显的答案。我想帮助审查这个“显而易见的”IOC 容器解决方案。各种对话假设策略的运行时选择和 IOC 容器的使用。我将继续这些假设。
我还想添加一个假设,即它不是必须选择的单一策略。相反,我可能需要检索一个对象图,该对象图在图的节点中具有多种策略。
我将首先快速概述两种常用的解决方案,然后我将介绍我希望看到 IOC 容器支持的“明显”替代方案。我将使用 Unity 作为示例语法,但我的问题并非针对 Unity。
命名绑定
这种方法要求每个新策略都手动添加一个绑定:
Container.RegisterType<IDataAccess, DefaultAccessor>();
Container.RegisterType<IDataAccess, AlphaAccessor>("Alpha");
Container.RegisterType<IDataAccess, BetaAccessor>("Beta");
...然后明确要求正确的策略:
var strategy = Container.Resolve<IDataAccess>("Alpha");
- 优点:简单,所有 IOC 容器都支持
- 缺点:
- 通常将调用者绑定到 IOC 容器,并且当然要求调用者了解策略的相关信息(例如名称“Alpha”)。
- 必须手动将每个新策略添加到绑定列表中。
- 此方法不适用于处理对象图中的多个策略。总之,不符合要求。
抽象工厂
为了说明这种方法,假设以下类:
public class DataAccessFactory{
public IDataAccess Create(string strategy){
return //insert appropriate creation logic here.
}
public IDataAccess Create(){
return //Choose strategy through ambient context, such as thread-local-storage.
}
}
public class Consumer
{
public Consumer(DataAccessFactory datafactory)
{
//variation #1. Not sufficient to meet requirements.
var myDataStrategy = datafactory.Create("Alpha");
//variation #2. This is sufficient for requirements.
var myDataStrategy = datafactory.Create();
}
}
然后 IOC 容器具有以下绑定:
Container.RegisterType<DataAccessFactory>();
- 优点:
- IOC 容器对消费者隐藏
- “环境上下文”更接近预期结果,但是...
- 缺点:
- 每种策略的构造器可能有不同的需求。但是现在构造函数注入的责任已经从容器转移到了抽象工厂。换句话说,每次添加新策略时,都可能需要修改相应的抽象工厂。
- 大量使用策略意味着大量创建抽象工厂。如果 IOC 容器能提供一点更多帮助,那就太好了。
- 如果这是一个多线程应用程序并且“环境上下文”确实由 thread-local-storage 提供,那么当一个对象使用注入的抽象工厂创建它需要的类型时,它可能是在不再有权访问必要的线程本地存储值的不同线程上运行。
类型切换/动态绑定
这是我想使用的方法,而不是上述两种方法。它涉及提供一个委托作为 IOC 容器绑定的一部分。大多数 IOC 容器都已经具备这种能力,但是这种特定的方法有一个重要的细微差别。
语法是这样的:
Container.RegisterType(typeof(IDataAccess),
new InjectionStrategy((c) =>
{
//Access ambient context (perhaps thread-local-storage) to determine
//the type of the strategy...
Type selectedStrategy = ...;
return selectedStrategy;
})
);
请注意,InjectionStrategy不是返回IDataAccess 的实例。相反,它返回实现IDataAccess 的类型描述。然后,IOC 容器将执行该类型的通常创建和“构建”,其中可能包括正在选择的其他策略。
这与标准的类型到委托的绑定形成对比,在 Unity 的情况下,它的编码如下:
Container.RegisterType(typeof(IDataAccess),
new InjectionFactory((c) =>
{
//Access ambient context (perhaps thread-local-storage) to determine
//the type of the strategy...
IDataAccess instanceOfSelectedStrategy = ...;
return instanceOfSelectedStrategy;
})
);
以上内容实际上接近于满足整体需求,但肯定达不到假设的 Unity InjectionStrategy。
专注于第一个样本(使用假设的 Unity InjectionStrategy):
- 优点:
- 隐藏容器
- 无需创建无休止的抽象工厂,也无需让消费者摆弄它们。
- 只要有新策略可用,就无需手动调整 IOC 容器绑定。
- 允许容器保留生命周期管理控制。
- 支持纯 DI 故事,这意味着多线程应用程序可以使用适当的线程本地存储设置在线程上创建整个对象图。
- 缺点:
- 由于在创建初始 IOC 容器绑定时该策略返回的
Type不可用,这意味着第一次返回该类型时可能会对性能造成很小的影响。换句话说,容器必须在现场反映类型以发现它具有哪些构造函数,以便它知道如何注入它。该类型的所有后续出现都应该很快,因为容器可以缓存它从第一次找到的结果。这几乎不是一个值得一提的“骗局”,但我正在努力全面披露。 - ???
- 由于在创建初始 IOC 容器绑定时该策略返回的
是否存在可以以这种方式运行的现有 IOC 容器?任何人都有实现此效果的 Unity 自定义注入类?
【问题讨论】:
-
您的
Type Switching / Dynamic Binding示例与Abstract Factory有何实际不同?您最终将不得不为两者编写几乎相同的代码。只是一个是一堆类,另一个是每个类型的一堆Container.RegisterType调用。
标签: c# .net dependency-injection ioc-container strategy-pattern