依赖注入

作为一条设计原则,依赖倒置原则(DIP)强调高层组件应该依赖于抽象而不是某个具体的实现或功能。控制反转(IoC)就是对依赖倒置原则的一个应用,用一段泛化的代码控制更加特定的外部组件的执行。

在一个控制反转解决方案中,方法通常会有一个或多个空缺点。每个空缺点的功能都由外部组件提供(静态的或者动态的),并通过 抽象接口调用。若满足了里氏替换原则和开放/封闭原则,那么改变任何外部组件都不会影响高层次的方法。外部组件和高层次方法可以分开开发。

控制反转的一个现实实例是Windows的shell扩展。当用户右键单击并选择“属性”命令时,Windows资源管理器会准备一个标准的对话框,随后执行一系列的控制反转。Windows将查看注册表,判断是否有已经注册过的自定义的属性页。若存在,Windows将通过一系列接口与这些扩展通信,并将页面添加至属性对话框中。

控制反转的另一个实现实例是事件驱动编程,起源于Visual Basic,现在已得到了Windows Form和Web Form的支持。例如,若编写了一个Button1_Click方法,并将其关联到Button1控件的Click事件上,那么也就是让Button类的代码(可重用且足够泛化)在用户单击时回调Button1_Click方法。

那么依赖注入(Dependency Injection,DI)又是什么呢?

从依赖倒置到依赖注入

在这里的讨论中,控制反转和依赖注入可以认为是同义词。不过在字面上二者并不总是同义词,控制反转有时会指导原则,而依赖注入则表明了原则的应用——即模式。实际上,控制反转历史上曾是基于依赖倒置原则的一个模式。依赖注入这个名词有Martin Fowler提出,用来专指控制反转的概念。

控制反转/依赖注入作为一个模式,可以让高层次方法不再需要辅助组件的具体引用。注入的过程可以通过三种方式来实现,一种是通过被注入方法所在的类的构造函数;第二种是通过在被注入方法所在的类中定义一个方法或者属性;最后一种是让被注入方法所在的类实现一个接口,接口中提供了辅助组件的具体实现。

目前,控制反转/依赖注入通常会随着某专门的框架提供,这些框架也提供了很多高级功能。

IoC框架

当前比较流行的几个Ioc框架。

Castle Windsor http://www.castleproject.org/container/index.html

Ninject http://www.ninject.org

Spring.Net http://www.springframework.net

StructureMap http://structuremap.sourceforge.net/Default.html

Unity http://codeplex.com/Unity

其中Ninject还可用于Silverlight和.NET Compact Framework。微软的Unity Application Block 是一个轻量级的IoC容器,支持构造函数、属性和方法的注入。Unity跟微软企业库一起发布。

所有的IoC容器框架有基于一个容器对象构造,这个容器对象将和一些配置信息绑定,并解析依赖。调用者代码将实例化容器,并将需要的接口以参数的形式传入,作为响应,Ioc/DI框架将返回一个实现了该接口的具体对象。

Ioc容器实战

假设某个类依赖于一个日志服务:

 

public class Task
    {
        private ILogger _logger;
        public Task(ILogger logger)
        {
            this._logger = logger;
        }
        public void Execute()
        {
            this._logger.Log("Begin method..");

            this._logger.Log("End method..");
        }
    }

 

Task类将通过构造函数接收日志组件,不过Task事故如何定位并初始化这个组件的呢?使用的静态new语句自然没有问题,这就要用到工厂。而IoC容器则是一个功能更加强大的框架,支持配置信息。

 

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
  </configSections>

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <alias alias="ILogger" type="MyApp.ILogger, MyApp" />
    <namespace name="MyApp.Implementations" />
    <assembly name="MyApp" />
    
    <container>
      <register type="ILogger" name="special" mapTo="DbLogger" />    
    </container>
  </unity>
</configuration>

 

配置文件(app.config或者web.config)中将包含接口和将要注入的具体类型之间的映射。当容器调用ILogger时,将会返回一个DbLogger类型实例。

 

    IUnityContainer container = new UnityContainer();
            UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");

            section.Containers.Default.Configure(container);
            ILogger logger = container.Resolve<ILogger>();
            Task t = new Task(logger);

 

对于测试而言,IoC/DI非常有用,因为它可以很容易地在不同的实现中切换。框架的使用让IoC/DI更加简单。通过脚本配置,可以让容器以单例的形式对待某个注入对象,这就意味着容器不会每次创建新的DbLogger实例,而是重用其唯一的实例。若DbLogger类是线程安全的话,那么的确能够大大提升效率。

此外,若DbLogger的构造函数需要引用到IoC/DI框架中注册的另一个类型,那么容器也能顺利地解析到该依赖。

 

Martin Fowler关于控制反转/依赖注入的论文

posted on 2012-06-22 21:27  Melou  阅读(658)  评论(0编辑  收藏  举报