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

相关文章

来自专栏逸鹏说道

C#进阶系列——WebApi 接口参数不再困惑:传参详解上

前言:还记得刚使用WebApi那会儿,被它的传参机制折腾了好久,查阅了半天资料。如今,使用WebApi也有段时间了,今天就记录下API接口传参的一些方式方法,算...

4068
来自专栏圣杰的专栏

ABP入门系列(4)——创建应用服务

一、解释下应用服务层 应用服务用于将领域(业务)逻辑暴露给展现层。展现层通过传入DTO(数据传输对象)参数来调用应用服务,而应用服务通过领域对象来执行相应的业务...

2797
来自专栏更流畅、简洁的软件开发方式

我的数据访问类(第二版)—— for .net2.0 (一)

asp.net2.0已经出来好久了,由于许多的原因一直没有使用,一个月前才开始使用VS2005写东西。 这一个月里又重新学习了一下基础知识,比如多态、接口了什么...

2009
来自专栏逸鹏说道

c# 温故而知新: 线程篇(一) 下

Abort 方法: 其实 Abort 方法并没有像字面上的那么简单,释放并终止调用线程,其实当一个线程调用 Abort方法时,会在调用此方法的线程上引发一个异常...

2576
来自专栏大内老A

[WCF REST] WebServiceHost有何特别之处?

WCF为REST服务的寄宿提供了一个新的ServiceHost,即WebServiceHost。WebServiceHost是ServiceHost的子类,而W...

2086
来自专栏小灰灰

Java 动手写爬虫: 二、 深度爬取

第二篇 前面实现了一个最基础的爬取单网页的爬虫,这一篇则着手解决深度爬取的问题 简单来讲,就是爬了一个网页之后,继续爬这个网页中的链接 1. 需求背景 背景...

60810
来自专栏老付的网络博客

HaspMap的原理

前几天有想法弄懂HashMap的实现的原理,我自己也YY了一个想法去实现一个简单的Map, 代码如下:

841
来自专栏.NET技术

.net平台的MongoDB使用

  最近花了点时间玩了下MongoDB.Driver,进行封装了工具库,平常也会经常用到MongoDB,因此写一篇文章梳理知识同时把自己的成果分享给大家。

1172
来自专栏林德熙的博客

C# 代码占用的空间

是不是代码会占用空间,如果一个程序初始化需要 100M 的代码,那么在他初始化之后,这些代码就没有作用了,他会不会占空间?本文经过测试发现,代码也是会占空间。

421
来自专栏Python攻城狮

Django教程(三)- Django表单Form1.Form 基本使用2.Form中字段及插件3.通过Django表单Form来完成需求4.自定义验证验证规则

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

1424

扫码关注云+社区