2010-06-09 70 views
29

我们想使用Unity for IOC。 我所看到的全部是有一个全局静态服务(让我们称之为IOCService)的实现,它持有对Unity容器的引用,该容器注册所有接口/类组合,并且每个类都会询问该对象:给我一个实施Ithis或IThat。如何在没有全局静态服务(非服务定位器解决方案)的情况下实现IOC?

通常我会看到这种模式不好的响应,因为它导致从所有类到IOCService(而不是Unity容器,因为它只在IOCService中知道)的依赖关系。

但我不经常看到的是:什么是替代方式?

米歇尔

编辑:发现了全局静态服务被称为服务定位器,补充说,为标题。

+0

开始赏金,对不起点的低量,但我只有一个赚了:-) – Michel 2010-06-20 12:21:59

+1

如果您想更详细的治疗我正在写一本关于它的书:http://www.manning.com/seemann/ – 2010-06-25 08:52:25

回答

11

另一种方法是有你的容器的最高应用水平只能为单一实例。,然后使用该容器来解决,你需要在该层创建的每一个对象实例

例如,大多数可执行文件的主要方法看起来就像这样(负异常处理):

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将创建它的所有依赖关系及其依赖关系的依赖关系,而不依赖于它们自己的依赖关系。


由于Web表单不支持构造函数注入,所以ASP.NET更加困难。我通常在我的Web表单应用程序中使用Model-View-Presenter,所以我的Page类在他们的演示者中实际上只有一个依赖关系。我不会单元测试它们(一切有趣和可测试的东西都在我的演示者中,我测试的是,我从来不会替换演示者。所以,我不打了框架 - 我只是公开一个容器属性上我HttpApplication类(在的global.asax.cs),直接从我的Page文件使用它:

protected void Page_Load(object sender, EventArgs args) { 
    ICustomerPresenter presenter = Global.Container.Resolve<ICustomerPresenter>(); 
    presenter.Load(); 
} 

那是当然的服务定位器 - 虽然Page类是耦合到定位器的唯一东西:您的演示者及其所有依赖项仍与您的IoC容器实现完全分离。

如果你有很大的依赖性在Page文件(即,如果你不使用模型 - 视图 - 演示),或者如果它是很重要的,你从你的Global应用程序类解耦Page类,你应该尝试找到一个集成到Web表单请求管道并使用属性注入的框架(如Nicholas在下面的评论中所建议的那样) - 或者编写自己的IHttpModule并自己执行属性注入。

+0

我以非常类似的方式执行此操作。就我而言,Global是IoCContainer,Container是GetInstance。这是整个应用程序中唯一一个手动编写单例的类(所有其他类都是单例或不依赖于Unity配置)。此类(Global/IoCContainer)位于名为Configuration的项目中。该项目仅由UI引用,并且它引用所有项目(UI除外)以便能够实例化适当的对象。 – bloparod 2010-06-09 14:34:43

+2

这仍然是经常建议的模式(服务定位器)。即使在ASP.NET WebForms中,也可以通过属性注入(http://code.google.com/p/autofac/wiki/AspNetIntegration#Implementing_WebForms_Pages_and_User_Controls)或通过重构演示者驱动的方法来实现依赖注入。 – 2010-06-09 21:27:55

+0

我同意尼古拉斯;除了这个答案听起来合乎逻辑,它仍然是一个服务定位器? – Michel 2010-06-10 08:23:38

5

不是明确使用容器,而是通过利用构造函数/属性注入来隐式使用它。创建一个依赖于应用程序所有主要部分的核心类(或一组核心类)。

大多数容器会让你把ISomething[]放在你的构造函数中,它会将ISomething的所有实例注入到你的类中。

这样,当你引导你的应用程序:

  1. 实例化容器
  2. 注册你所有的好东西
  3. 解决的核心类(这将拉在你需要的所有其他相关性)
  4. 运行应用程序的“主要”部分

现在,根据您的应用程序类型正在编写,有不同的策略来避免将IoC容器标记为“静态”。

对于ASP.NET Web应用程序,您最终可能会将容器存储在应用程序状态中。对于ASP.NET MVC应用程序,您需要更换Controller Factory。

对于桌面应用程序,事情变得更加复杂。 Caliburn使用一个有趣的解决方案,以使用IResult结构(这是WPF应用程序,但可以适用于Windows窗体以及这个问题。

+0

Unity在构造函数解析中不会解析'ISomething []'或'IEnumerable '。如果您需要传递已注册依赖项的列表,您必须使用'InjectionConstructor'。这种情况很糟糕,因为这意味着这些依赖项在启动时会解决一次,因此不允许瞬态生命周期。很高兴知道是否有更好的解决方案。 – 2010-06-10 13:07:03

+0

如果您需要在运行时将新实例注入代码,那么您应该有明确的方式(不是由IoC容器注入)发生。您可以设置一个事件聚合器来通知特定类型的新实例何时到达。 – 2010-06-23 23:25:38

+0

@Igor - 你也可以通过注入一个可以在运行时创建新的瞬态实例的工厂来解决这个问题。 – 2010-06-24 13:54:40

2

如果您的问题在整个应用程序中对Unity具有依赖性,您可以将服务定位器与外观结合以隐藏IOC实现。通过这种方式,您不需要在应用程序中创建Unity的依赖关系,只需要即可为您解析类型的东西

例如:

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>(); 
    } 
} 

现在,所有你需要的是一个​​实施您选择的IOC容器。

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>(); 
    } 
} 

现在您有一个服务定位器,它是IOC服务的外观,并且可以配置您的服务定位器以使用Unity或任何其他IOC容器。应用程序的其余部分不依赖于IOC实现。

要配置的服务定位:

IUnityContainer unityContainer = new UnityContainer(); 
UnityContainerImplementation containerImpl = new UnityContainerImplementation(unityContainer); 
Container.InitializeWith(containerImpl); 

为了进行测试,您可以创建​​存根返回任何你想要的,并初始化Container这一点。

+5

服务定位器*是问题(至少对于很多人来说),而不仅仅是对特定依赖注入容器的具体依赖。 Mark Seemann [已经很清楚地写了服务定位器的问题](http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx)。 – 2010-06-10 13:39:19

+0

确实不错,但很多人对服务定位器模式有问题(因为它创建了服务定位器类的依赖关系,而不仅仅是Unity类),但问题是我从来没有看到一个好的替代方案,这就是为什么我问。 – Michel 2010-06-10 15:26:18

+0

@Jeff:你链接中的文章很好 – Michel 2010-06-10 15:32:31

4

理论上,不必担心拥有静态IoC实例,您需要遵循搏击俱乐部规则 - 即不要谈论战斗俱乐部 - 即不提及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>(); 

不那么简单的情况就是要注入多个注册。特别适用于任何类型的Converntion Over Configuration并通过名称创建对象。

有关付款例如,假设你想通过所有帐户枚举并检查他们的余额:

class PaymentService { 

    IEnumerable<IAccount> accounts; 

    public PaymentService (IEnumerable<IAccount> accounts) { 
    this.accounts = accounts; 
    } 

    public void ProcessPayment() { 
    foreach(var account in accounts) { 
     account.Chackbalance(); 
    } 
    } 
} 

团结有注册多个接口类的映射(他们必须有不同的名字还以为)的能力。但是,它不会自动将它们注入到那些注册接口集合的类中。所以,上面的例子会在运行时抛出一个解决失败的异常。

如果你不在乎这些对象永远活着,你可以在一个更静态的方式登记PaymentService

container.RegisterType<PaymentService>(new InjectionConstructor(container.ResolveAll<IAccount>())); 

上面的代码将注册PaymentService并会使用IAccount实例的集合,即解决在注册时。

或者,您可以传递容器本身的一个实例作为依赖关系,并让PaymentService执行帐户的解析。这并不完全符合扑克俱乐部规则,但比静态服务定位器稍微有点臭。

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); 
7

+1 知道即服务定位是一件坏事

问题是 - Unity不是非常复杂,所以我不知道它是多么容易/难以用它正确地使用IoC。

最近我写了几篇博文,你可能会觉得有用。

+0

+1这些文章总结得非常好。 Unity理解构造函数注入,所以第一篇文章直接翻译。 AFAIK,Unity不支持动态发布的工厂,但关于Abstract Factory的使用模式仍然适用 - 您只需手动实现具体的工厂,但这样做非常微不足道。 – 2010-06-25 08:51:05

+0

Unity 2.0支持基于委托的自动工厂。 – 2010-08-24 00:19:24

相关问题