如何在没有全局静态服务(非服务定位器解决方案)的情况下实现IOC?

时间:2010-06-09 14:08:25

标签: c# inversion-of-control unity-container ioc-container

我们希望将Unity用于IOC。 我所看到的是有一个全局静态服务(我们称之为IOCService)的实现,它包含对Unity容器的引用,该容器注册所有接口/类组合,每个类都询问该对象:给我一个实现为Ithis或IThat。

我经常看到这种模式不好的响应,因为它导致从ALL类到IOCService的依赖(而不是Unity容器,因为它只在IOCService内部知道)。

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

米歇尔

编辑:发现全局静态服务被称为服务定位器,将其添加到标题中。

5 个答案:

答案 0 :(得分: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将创建它的所有依赖项 - 及其依赖项的依赖项,一直到叶子没有自己依赖的类。


ASP.NET是一个更难的案例,因为Web表单不支持构造函数注入。我通常在我的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文件中有很多依赖项(即,如果您不使用Model-View-Presenter),或者您要将Page类与您的Global应用程序类,您应该尝试找到一个集成到Web表单请求管道中并使用属性注入的框架(如下面的评论中的Nicholas所建议的那样) - 或编写您自己的IHttpModule并执行财产注入自己。

答案 1 :(得分:7)

+1 了解服务定位器是一件坏事

问题是 - Unity不是很复杂,所以我不知道以正确的方式使用它是多么容易/多难。

我最近写了一些博客文章,你可能觉得它很有用。

答案 2 :(得分:5)

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

大多数容器都允许您将ISomething[]放入构造函数中,它会将ISomething的所有实例注入您的类中。

这样,当您引导应用程序时:

  1. 实例化容器
  2. 注册所有好吃的东西
  3. 解析核心类(这将引入您需要的所有其他依赖项)
  4. 运行应用程序的“主要”部分
  5. 现在,根据您编写的应用程序类型,有不同的策略可以避免将IoC容器标记为“静态”。

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

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

答案 3 :(得分: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();
    }
  }
}

Unity能够为类映射注册多个接口(它们必须考虑不同的名称)。但是,它不会自动将这些注入到接收这些已注册接口集合的类中。因此,上面的示例将在运行时抛出一个解决方案失败的异常。

如果您不关心这些对象永远存在,您可以以更静态的方式注册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);

答案 4 :(得分: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容器实现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>();
    }
}

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

配置服务定位器:

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

为了进行测试,您可以创建一个IContainer的存根,它可以返回您想要的任何内容,并使用它初始化Container