依赖注入(IOC)

背景介绍

在设计模式中,尤其是结构型模式很多时候解决的就是对象间的依赖关系,变依赖具体为依赖抽象。平时开发中如果发现客户程序依赖某个或某类对象,我们常常会对他们进行一次抽象,形成抽象的抽象类、接口,这样客户程序就可以摆脱所依赖的具体类型。 这个过程中有个环节被忽略了------谁来选择客户程序需要的满足抽象类型的具体类型呢?通过后面的介绍你会发现很多时候创建型模式可以比较优雅的解决这个问题,但另一个问题出现了,如果您设计的不是具体的业务逻辑,而是公共库或框架程序呢,这时候你是一个‘服务方’,不是你调用那些构造类型,而是它们把抽象类型传给你,怎么松散地把加工好的抽象类型传递给客户程序就是另一回事了。 这个情形也就是常说的“控制反转”,IOC:Inverse of Control;框架程序与抽象类型的调用关系就像常说的好莱坞规则:Don’t call me,I’ll  call you

示例场景

       客户程序需要一个提供System.DateTime类型当前系统时间的对象,然后根据需要仅仅把其中的年份部分提取出来,因此最初的代码如下:

public class TimeProvider { 
public DateTime CurrentDate { 
get { 
return DateTime.Now;
 } 
         } 
               } 
 public class Client { public int GetYear() { TimeProvider timeprovider = new TimeProvider();
 return timeprovider.CurrentDate.Year;
         } 
  }

    后来某种原因,发现使用.NET Framework自带的日期类型精度不够,需要提供其他来源的TimeProvider,确保在不同精度要求的功能模块中使用不同的TimeProvider。这样问题集中在TimeProvider的变化会影响客户程序,但其实客户程序仅需要抽象地使用其获取当前时间的方法。为此,增加一个抽象接口ITimeProvider,改造后的示例如下:

public interface ITimeProvider { public DateTime CurrentDate { get ; } } public class TimeProvider:ITimeProvider { public DateTime CurrentDate { get { return DateTime.Now; } } } public class Client { public int GetYear() { ITimeProvider timeprovider = new TimeProvider(); return timeprovider.CurrentDate.Year; } }

这样看上去客户程序后续处理全部依赖于抽象的ITimeProvider就可以了,那么问题是否解决了呢?没有,因为客户程序还要知道SystemTimeProvider的存在。因此,需要增加一个对象,由它选择某种方式把ITimeProvider实例传递给客户程序,这个对象被称为Assembler.

对于依赖注入而言,Assembler的作用很关键,因为它解决了客户程序(也就是注入类型)与待注入实体类型间的依赖关系,从此Client只需要依赖ITimeProvider和Assembler即可,它并不知道TimeProviderImpl的存在。

Assembler的职责如下:

  • 知道每个具体的TimeProviderImpl的类型。
  • 根据客户程序的需要,将对象ITimeProvider反馈给客户程序。
  • 负责对TimeProviderImpl实例化。

下面是一个Assembler的示例实现:

public class Assembler { 
  //保存“抽象类型/实体类型"对应关系的字典 
  static Dictionary<Type, Type> dictionary = new Dictionary<Type, Type>(); 
  static Assembler() { 
    //注册抽象类型需要使用的实体类型 
    //实际配置信息可以从外层机制获得,例如通过配置定义 
    dictionary.Add(typeof(ITimeProvider), typeof(SystemTimeProvider)); 
  } 
    /// <summary> /// 根据客户程序需要的抽象类型选择相应的实体类型,并返回类型的实例 
    /// </summary> 
    /// <param name="type"></param> 
    /// <returns>实体类型实例</returns> 
    public object Create(Type type)//主要用于非泛型方式调用
     { 
       if ((type == null) || !dictionary.ContainsKey(type)) 
         throw new NullReferenceException(); 
        return Activator.CreateInstance(dictionary[type]); 
      } 
  /// <summary> /// 
  /// </summary> /// <typeparam name="T">抽象类型(抽象类/接口/或者某种基类)</typeparam>
  /// <returns></returns> 
  public T Create<T>()//主要用于泛型方式调用 
    { 
      return (T)Create(typeof(T)); } 
    }
}

构造注入(Constructor)

构造注入方式又称“构造子注入”、“构造函数注入”,顾名思义,这种注入方式就是在构造函数的执行过程中,通过Assembler或其它机制把抽象类型作为参数传递给客户类型。这种方式虽然相对其它方式有些粗糙,而且仅在构造过程中通过“一锤子买卖”的方式设置好,但很多时候我们设计上正好就需要这种“一次性”的注入方式。

其实现方式如下:

//在构造函数中注入 public class Client { ITimeProvider timerprovider; public Client(ITimeProvider timeProvider) { this.timerprovider = timeProvider; } }UnitTest [TestClass] public class TestClent { [TestMethod] public void TestMethod1() { ITimeProvider timeProvider = (new Assembler()).Create<ITimeProvider>(); Assert.IsNotNull(timeProvider);//确认可以正常获得抽象类型实例 Client client = new Client(timeProvider);//在构造函数中注入 } }

设值注入(Setter)

设值注入是通过属性方法赋值的办法实现的。相对于构造方式而言,设值注入给了客户类型后续修改的机会,它比较适合于客户类型实例存活时间较长的情景。

实现方式如下:

//通过Setter实现中注入 public class Client { public ITimeProvider TimeProvider { get; set; } }

Unit Test

[TestClass] public class TestClent { [TestMethod] public void TestMethod1() { ITimeProvider timeProvider = (new Assembler()).Create<ITimeProvider>(); Assert.IsNotNull(timeProvider);//确认可以正常获得抽象类型实例 Client client = timeProvider;//通过Setter实现注入 } }

从C#语言发展看,设置注入方式更”Lamada化“,使用时可以根据现场环境需要动态装配,因此在新项目中我更倾向于使用设置注入。这个例子更时髦的写法如下:

[TestClass] public class TestClent { [TestMethod] public void TestMethod1() { var clent = new Client() { TimeProvider = (new Assembler()).Create<ITimeProvider>() }; } }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏GreenLeaves

控制反转和依赖注入模式

一、控制反转和依赖注入两者搭配能像反射工厂那样解决程序集之间的耦合问题,下面将从Asp.Net经典的三层模式多方位的讲解控制反转和依赖注入模式,是如何帮我们进行...

17210
来自专栏恰同学骚年

ch01.深入理解C#委托及原理_《没有控件的ASPDONET》

设想,如果我们写了一个厨师做菜方法用来做菜,里面有 拿菜、切菜、配菜、炒菜 四个环节,但编写此方法代码的人想让 配菜 这个环节让调用方法的人实现,换句话说,就是...

553
来自专栏GreenLeaves

CLR关于语言文化的类型一CultureInfo类和字符串与线程的关联

1054
来自专栏跟着阿笨一起玩NET

使用dynamic来简化反射实现,并且提高了性能。

本人转载:http://www.cnblogs.com/cuitsl/archive/2012/01/06/2314636.html

541
来自专栏技术博客

设计模式之四(抽象工厂模式第三回合)

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

882
来自专栏跟着阿笨一起玩NET

Unit Test单元测试时如何模拟HttpContext

参考文章:http://blog.csdn.net/bclz_vs/article/details/6902638

191
来自专栏blackheart的专栏

[C#1] 10-事件

事件概述 CLR的事件模型建立在委托的机制之上。定义事件成员的类型允许类型(或者类型的实例)在某些特定事件发生时通知其他对象,事件为类型提供了一下三种能力: 1...

1806
来自专栏炉边夜话

JNI设计实践之路

本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。本文中的示例使用 Sun公司的 Java Development...

693
来自专栏开发技术

spring-boot-2.0.3不一样系列之源码篇 - springboot源码一,绝对有值得你看的地方

  上篇:spring-boot-2.0.3不一样系列之shiro - 搭建篇,实现了spring-boot与shiro的整合,效果大家也看到了,工程确实集成了...

682
来自专栏程序员的SOD蜜

在C++中反射调用.NET(一) 反射调用第一个.NET类的方法

为什么要在C++中调用.NET 一般情况下,我们常常会在.NET程序中调用C/C++的程序,使用P/Invoke方式进行调用,在编写代码代码的时候,首先要导入D...

21610

扫描关注云+社区