依赖注入(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 条评论
登录 后参与评论

相关文章

来自专栏大内老A

依赖注入[5]: 创建一个简易版的DI框架[下篇]

为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架。在《依赖注入[4]: 创建...

593
来自专栏大内老A

集成EntLib实现ASP.NET MVC的异常处理[续篇]

在《集成EntLib实现ASP.NET MVC的异常处理》我们实现采用EntLib的Exception Handling Application Block(E...

1839
来自专栏Java成神之路

【转】零基础写Java知乎爬虫之进阶篇

说到爬虫,使用Java本身自带的URLConnection可以实现一些基本的抓取页面的功能,但是对于一些比较高级的功能,比如重定向的处理,HTML标记的去除,仅...

903
来自专栏菩提树下的杨过

“default关键字”与“序列化传输”的注意事项

注:此乃“流水帐”式的水文,营养成分较低,高手请自动无视以下内容,否则引起消化不良等症状等,一概不管 ^_^ c#自从3.0开始,提供了很多便捷的语法特性(俗称...

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

树TreeView控件与DataTable交互添加节点(最高效的方法)

本文转载:http://blog.csdn.net/q107770540/article/details/7708418

1011
来自专栏『不羁阁』行走的少年专栏

OC知识--Foundation框架及相关类详尽总结

1144
来自专栏函数式编程语言及工具

Akka(15): 持久化模式:AtLeastOnceDelivery-消息保证送达模式

  消息保证送达是指消息发送方保证在任何情况下都会至少一次确定的消息送达。AtleastOnceDelivery是一个独立的trait,主要作用是对不确定已送达...

2525
来自专栏大内老A

在ASP.NET MVC中如何应用多个相同类型的ValidationAttribute?

ASP.NET MVC采用System.ComponentModel.DataAnnotations提供的元数据验证机制对Model实施验证,我们可以在Mode...

1885
来自专栏智能大石头

C++返回值优化RVO

返回值优化,是一种属于编译器的技术,它通过转换源代码和对象的创建来加快源代码的执行速度。RVO = return value optimization。 测试...

2199
来自专栏大内老A

我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承

当今的IT领域,SOA已经成为了一个非常时髦的词,对SOA风靡的程度已经让很多人对SOA,对面向服务产生误解。其中很大一部分人甚至认为面向服务将是面向对象的终结...

1715

扫码关注云+社区