我们希望在Unity中使用IOC。我看到的实现方式是有一个全局静态服务(我们称之为IOCService),它持有对Unity容器的引用,该容器注册所有的接口/类组合,每个类都会请求该对象:给我一个Ithis或IThat的实现。
经常听到回应说这种模式不好,因为它导致所有类都依赖于IOCService(而不是Unity容器,因为它只在IOCService内部知道)。
但是我不经常看到的是: 有什么替代方法吗?
- Michel
编辑:发现全局静态服务被称为服务定位器,已将其添加到标题中。
我们希望在Unity中使用IOC。我看到的实现方式是有一个全局静态服务(我们称之为IOCService),它持有对Unity容器的引用,该容器注册所有的接口/类组合,每个类都会请求该对象:给我一个Ithis或IThat的实现。
经常听到回应说这种模式不好,因为它导致所有类都依赖于IOCService(而不是Unity容器,因为它只在IOCService内部知道)。
但是我不经常看到的是: 有什么替代方法吗?
- Michel
编辑:发现全局静态服务被称为服务定位器,已将其添加到标题中。
另一种选择是仅在应用程序的最高级别拥有一个容器实例,然后使用该容器来解析您需要在该层中创建的每个对象实例。
例如,大多数可执行文件的主方法看起来就像这样(减去异常处理):
private static void main(string[] args) {
Container container = new Container();
// Configure the container - by hand or via file
IProgramLogic logic = container.Resolve<IProgramLogic>();
logic.Run();
}
你的程序(在这里由IProgramLogic
实例表示)不需要知道容器的任何信息,因为container.Resolve
将创建所有它的依赖项 - 以及其依赖项的依赖项,一直到没有自己依赖项的叶类。
ASP.NET是一个更难的情况,因为Web Forms不支持构造函数注入。我通常在我的Web Forms应用程序中使用Model-View-Presenter,因此我的Page
类只有一个依赖项 - 它们的Presenter。我不对它们进行单元测试(所有有趣和可测试的都在我的Presenter中,我进行测试),我也不替换Presenter。因此,我不与框架斗争 - 我只在我的HttpApplication
类(在global.asax.cs中)上公开容器属性,并直接从我的Page
文件中使用它:
protected void Page_Load(object sender, EventArgs args) {
ICustomerPresenter presenter = Global.Container.Resolve<ICustomerPresenter>();
presenter.Load();
}
那当然是服务定位器 - 虽然 Page
类是唯一与定位器相耦合的东西:您的 Presenter 和其所有依赖项仍然完全与您的 IoC 容器实现解耦。如果您的 Page
文件中有许多依赖项(即,如果您不使用 Model-View-Presenter),或者如果将您的 Page
类从您的 Global
应用程序类中解耦对您很重要,那么您应该尝试找到一个集成到 Web 表单请求管道中的框架,并使用属性注入(如下方评论中由 Nicholas 建议)-或编写自己的 IHttpModule
并手动执行属性注入。+1 是为了表明知道Service Locator是不好的事情。
问题在于 - Unity并不是非常复杂,所以我不知道使用正确的方式进行IoC有多容易/困难。
我最近写了一些博客文章,您可能会发现它们很有用。
不要直接使用容器,而是通过构造函数/属性注入隐式地使用它。创建一个核心类(或一组核心类),这些类依赖于应用程序的所有主要部分。
大多数容器都允许您在构造函数中放置ISomething[]
,并将所有ISomething
实例注入到您的类中。
这样,当您引导应用程序时:
现在,根据您编写的应用程序类型,有不同的策略可避免将IoC容器标记为“静态”。
对于ASP.NET Web应用程序,您可能最终会将容器存储在应用程序状态中。对于ASP.NET MVC应用程序,您需要更改控制器工厂。
对于桌面应用程序,情况变得更加复杂。 Caliburn 使用了一个有趣的解决方案,使用IResult构造(这适用于WPF应用程序,但也可以适应Windows Forms)。
ISomething[]
或IEnumerable<ISomething>
。如果需要传递已注册依赖项列表,则必须使用InjectionConstructor
。这有点糟糕,因为这意味着这些依赖项在启动时只解析一次,因此不允许瞬态生命周期。如果有更好的解决方案,我会很高兴知道。 - Igor Zevaka理论上,为了不必担心有一个静态的IoC实例,您需要遵循Fight Club Rule——即不要谈论打斗俱乐部——即不要提及IoC容器。
这意味着您的组件在很大程度上不应该了解IoC容器。只有在注册组件时才应使用它。如果一个类需要解析某些东西,它应该作为依赖项注入。
简单情况是足够简单的。如果PaymentService
依赖于IAccount
,则后者应由IoC注入:
interface IAccount {
Deposit(int amount);
}
interface CreditCardAccount : IAccount {
void Deposit(int amount) {/*implementation*/}
int CheckBalance() {/*implementation*/}
}
class PaymentService {
IAccount account;
public PaymentService (IAccount account) {
this.account = account;
}
public void ProcessPayment() {
account.Deposit(5);
}
}
//Registration looks something like this
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();
不太简单的情况是您想要注入多个注册信息。特别是在执行任何形式的约定优于配置并从名称创建对象时,这种情况尤其适用。
以我们的付款示例为例,假设您想枚举所有帐户并检查它们的余额:
class PaymentService {
IEnumerable<IAccount> accounts;
public PaymentService (IEnumerable<IAccount> accounts) {
this.accounts = accounts;
}
public void ProcessPayment() {
foreach(var account in accounts) {
account.Chackbalance();
}
}
}
Unity有能力将多个接口映射到同一类中(但它们必须具有不同的名称)。然而,它不会自动将这些已注册的接口集合注入到需要这些接口集合的类中。因此,上面的示例在运行时会抛出解析失败的异常。
如果您不关心这些对象是否永久存在,可以以更静态的方式注册PaymentService
:
container.RegisterType<PaymentService>(new InjectionConstructor(container.ResolveAll<IAccount>()));
PaymentService
并使用在注册时解析的IAccount
实例集合。PaymentService
执行帐户的解析。 这不完全遵循Fight Club规则,但比静态服务定位器更少臭味。class PaymentService {
IEnumerable<IAccount> accounts;
public PaymentService (IUnityContainer container) {
this.accounts = container.ResolveAll<IAccount>();
}
public void ProcessPayment() {
foreach(var account in accounts) {
account.Chackbalance();
}
}
}
//Registration is pretty clean in this case
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();
container.RegisterInstance<IUnityContainer>(container);
public interface IContainer
{
void Register<TAbstraction,TImplementation>();
void RegisterThis<T>(T instance);
T Get<T>();
}
public static class Container
{
static readonly IContainer container;
public static InitializeWith(IContainer containerImplementation)
{
container = containerImplementation;
}
public static void Register<TAbstraction, TImplementation>()
{
container.Register<TAbstraction, TImplementation>();
}
public static void RegisterThis<T>(T instance)
{
container.RegisterThis<T>(instance);
}
public static T Get<T>()
{
return container.Get<T>();
}
}
IContainer
实现。public class UnityContainerImplementation : IContainer
{
IUnityContainer container;
public UnityContainerImplementation(IUnityContainer container)
{
this.container = container;
}
public void Register<TAbstraction, TImplementation>()
{
container.Register<TAbstraction, TImplementation>();
}
public void RegisterThis<T>(T instance)
{
container.RegisterInstance<T>(instance);
}
public T Get<T>()
{
return container.Resolve<T>();
}
}
IUnityContainer unityContainer = new UnityContainer();
UnityContainerImplementation containerImpl = new UnityContainerImplementation(unityContainer);
Container.InitializeWith(containerImpl);
为了测试,你可以创建一个IContainer
的存根,返回任何你想要的内容,并使用它来初始化Container
。