第2章 IoC的基本概念

让别人为你服务

IoC的全称是Inversion of Control,中文通常翻译为“控制反转”,它还有一个别名叫依赖注入(Dependency Injection)。

假定有一提供外汇(Foreign Exchange)新闻的项目,该项目功能是爬取指定网站新闻并存储到本地,最终在FX系统前台展示,这些功能由FXNewsProvider类来完成。

来看下代码:

public class FXNewsProvider {// 抓取新闻类private IFXNewsListener newsListener;// 存储新闻类private IFXNewsPersister newPersistener;// 构造器public FXNewsProvider() {newsListener = new DowJonesNewsListener();newPersistener = new DowJonesNewsPersister(); }// 抓取新闻和存储的方法public void getAndPersistNews() {// 获取所有可用新闻id数组String[] newsIds = newsListener.getAvailableNewsIds();if (ArrayUtils.isEmpty(newsIds)) {return;}// 循环每条新闻存储进数据库并发布for (String newsId : newsIds) {FXNewsBean newsBean = newsListener.getNewsByPK(newsId);newPersistener.persistNews(newsBean);newsListener.postProcessIfNecessary(newsId);}}
}

我在关键行加了一些注释,可以看到首先我们定义了两个成员变量,一个用于抓取新闻内容,一个用于存储新闻。并且在构造器中,我们提供了DowJones道琼斯新闻社的实现类(DowJonesNewsListener和DowJonesNewsPersister)。这两个类是FXNewsProvider这个新闻提供类所要依赖的类,因此可以被称为“依赖类”、“依赖对象”。

如果我们依赖于某个类,我们最常用的方法就是new它,直接在构造器中新建相应的依赖类,也就是说我们需要主动去获取依赖的对象

可是回头想想,我们自己每次用到什么依赖对象都要主动地去获取,这是否真的必要?我们最终所要做的,其实就是直接调用依赖对象所提供的某项服务而已。只要用到这个依赖对象的时候,它能够准备就绪,我们完全可以不管这个对象是自己找来的还是别人送过来的。对于FXNewsProvider来说,那就是在getAndPersistNews()方法调用newsListener的相应方法时,newsListener能够准备就绪就可以了。如果有人能够在我们需要时将某个依赖对象送过来,为什么还要大费周折地自己去折腾?

实际上,IoC就是为了帮助我们避免之前的“大费周折”,而提供了更加轻松简洁的方式。它的反转,就反转在让你从原来的事必躬亲,转为现在的享受服务。所以,简单点儿说,IoC的理念就是,让别人为你服务!在图2-1中,也就是让IoC Service Provider来为你服务!

上面的FXNewsProvider就是被注入对象,它被注入了DowJonesNewsListener抓取类和DowJonesNewsPersister存储类,而这两个类也是被依赖对象,FXNewsProvider依赖它们俩。现在,被注入对象需要什么都由IoC Service Provider来提供,与之前直接寻求依赖相比,依赖对象的取得方式发生了反转。

三种依赖注入方式

由三种依赖注入的方式,即构造方法注入(constructor injection)、setter方法注入(setter injection)以及接口注入(interface injection)。

举例子:

当你来到酒吧,想要喝杯啤酒的时候,通常会直接招呼服务生,让他为你送来一杯清凉解渴的啤酒。同样地,作为被注入对象,要想让IoC Service Provider为其提供服务,并将所需要的被依赖对象送过来,也需要通过某种方式通知对方。

如果你是酒吧的常客,或许你刚坐好,服务生已经将你最常喝的啤酒放到了你面前;

如果你是初次或偶尔光顾,也许你坐下之后还要招呼服务生过来;

还有一种可能,你根本就不知道哪个牌子是哪个牌子,这时,你只能打手势或干脆画出商标图来告诉服务生你到底想要什么!

构造方法注入

构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表, 让外部(通常是IoC容器)知道它需要哪些依赖对象。

public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {this.newsListener = newsListner;this.newPersistener = newsPersister; }
}

**IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。**构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。这就好比你刚进酒吧的门,服务生已经将你喜欢的啤酒摆上了桌面一样。

setter方法注入

当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。以FXNewsProvider为例:

public class FXNewsProvider {private IFXNewsListener newsListener;private IFXNewsPersister newPersistener;public void setNewsListener(IFXNewsListener newsListener) {this.newsListener = newsListener;}public void setNewPersistener(IFXNewsPersister newPersistener) {this.newPersistener = newPersistener;}...
}

这样,外界就可以通过调用setNewsListener和setNewPersistener方法为FXNewsProvider对 象注入所依赖的对象了。

setter方法注入虽不像构造方法注入那样让对象构造完成后即可使用,但相对来说更宽松一些,可以在对象构造完成后再注入。这就好比你可以到酒吧坐下后再决定要点什么啤酒,随意性比较强。如果你不急着喝,这种方式当然是最适合你的。

接口注入

被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。 IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。

实现的接口和接口中声明的方法名称都不重要。重要的是接口中声明方法的参数类型,必须是“被注入对象”所依赖对象的类型。

它强制被注入对象实现不必要的接口,带有侵入性,已经不推荐使用了

IoC的附加值

从主动获取依赖关系的方式转向IoC方式,不只是一个方向上的改变,简单的转变背后实际上蕴藏着更多的玄机。要说IoC模式能带给我们什么好处,可能各种资料或书籍中已经罗列很多了。比如不会对业务对象构成很强的侵入性,使用IoC后,对象具有更好的可测试性、可重用性和可扩展性,等等。

对于前面例子中的FXNewsProvider来说,当系统中需要追加逻辑以处理另一家新闻社的新闻来源时,问题就来了。

假定新闻社叫MarketWin24。这个时候,你该如何处理呢?首先,毫无疑问地,应该先根据MarketWin24的服务接口提供一个MarketWin24NewsListener实现,用来接收新闻;其次,因为都是相同的数据访问逻辑, 所以原来的DowJonesNewsPersister可以重用,我们先放在一边不管。最后,就主要是业务处理对象 FXNewsProvider了。因为我们之前没有用IoC,所以,现在的对象跟DowJonesNewsListener是绑定的,我们无法重用这个类了,不是吗?为了解决问题,我们可能要重新实现一个继承自 FXNewsProvider的MarketWin24NewsProvider,或者干脆重新写一个类似的功能。

而使用IoC后,面对同样的需求,我们却完全可以不做任何改动,就直接使用FXNewsProvider。 因为不管是DowJones还是MarketWin24,对于我们的系统来说,处理逻辑实际上应该是一样的:根据 各个公司的连接接口取得新闻,然后将取得的新闻存入数据库。因此,我们只要根据MarketWin24的新闻服务接口,为MarketWin24的FXNewsProvider提供相应的MarketWin24NewsListener注入就可以了,见代码:

FXNewsProvider dowJonesNewsProvider = ➥
new FXNewsProvider(new DowJonesNewsListener(),new DowJonesNewsPersister());
...
FXNewsPrivider marketWin24NewsProvider = ➥
new FXNewsProvider(new MarketWin24NewsListener(),new DowJonesNewsPersister());
...

使用IoC之后,FXNewsProvider可以重用,而不必因为添加新闻来源去重新实现新的 FXNewsProvider。实际上,只需要给出特定的IFXNewsListener实现即可。

随着开源项目的成功,TDD(Test Driven Developement ,测试驱动开发)已经成为越来越受重视的一种开发方式。对于软件开发来说,设计可测试性良好的业务对象是至关重要的。而IoC模式可以让我们更容易达到这个目的。比如,使用IoC模式后,为了测试 FXNewsProvider,我们可以根据测试的需求,提供一个MockNewsListener给FXNewsProvider。在以前不用IoC的时候,我们无法将对DowJonesNewsListener的依赖排除在外,从而导致难以开展单元测试。而现在,单元测试则可以毫无牵绊地进行,下面代码演示了测试取得新闻失败的情形。

定义一个获取新闻失败的listener:

public class MockNewsListener implements IFXNewsListener {public String[] getAvailableNewsIds() {throw new FXNewsRetrieveFailureException();}public FXNewsBean getNewsByPK(String newsId) {// TODOreturn null;}public void postProcessIfNecessary(String newsId) { // TODO}
}

相应的FXNewsProvider的单元测试类:

public class FXNewsProviderTest {private FXNewsProvider newsProvider;@Overrideprotected void setUp() throws Exception {super.setUp();newsProvider = new FXNewsProvider(new MockNewsListener(), new MockNewsPersister());}@Overrideprotected void tearDown() throws Exception {super.tearDown();newsProvider = null;}public void testGetAndPersistNewsWithoutResourceAvailable() {try {newsProvider.getAndPersistNews();fail("Since MockNewsListener has no news support, ➥ we should fail to get above.");} catch (FXNewsRetrieveFailureException e) {//......}}
}

疑问:为什么说以前无法排除DowJonesNewsListener依赖?

解答:如果不用IoC,就是主动寻求依赖的方式,代码是这样的:

public FXNewsProvider() {newsListener = new DowJonesNewsListener();newPersistener = new DowJonesNewsPersister();
}

可以看到DowJonesNewsListener被我们硬编码写入的,并赋值给了成员变量newsListener,而在使用了IoC容器的情况下,可以通过构造器将MockNewsListener注入进去,关键代码是newsProvider = new FXNewsProvider(new MockNewsListener(), new MockNewsPersister());

可见,IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。

第2章 IoC的基本概念相关推荐

  1. 第二章 单元测试的基本概念和核心技法

    第二章 单元测试的基本概念和核心技法 2.1 良好的单元测试--定义 我们已经了解了程序员需要单元测试,下面我们来给单元测试作一个完整的定义: ● 定义: 单元测试是一段自动执行的代码,它调用被测类或 ...

  2. 【控制】《自动控制原理》胡寿松老师-第1章-自动控制的一般概念

    无 回到目录 第2章 第1章-自动控制的一般概念 1.1 自动控制的基本原理与方式 1.2 自动控制系统示例 1.3 自动控制系统的分类 1.4 对自动控制系统的基本要求 1.5 自动控制系统的分析与 ...

  3. 概率统计:第一章 概率论的基本概念

    第一章   概率论的基本概念 内容提要: 一. 加法.乘法原理及排列.组合复习 1.  加法原理  设完成一件事有类方法(其中任一类方法都可达到 完成这件事的目的),若第1类方法有种,第2类方法有种, ...

  4. 概率论-第一章 概率论的基本概念

    目录 概率论-第一章 概率论的基本概念 (1)随机试验 总结 (2)样本空间.随机试验 总结 (3)频率和概率 总结 (4)等可能概型(古典概型) 总结 (5)条件概率 总结 (6)独立性 总结 本章 ...

  5. 《AutoCAD全套园林图纸绘制自学手册》一第1章 园林设计基本概念1.1 概述

    本节书摘来自异步社区<AutoCAD全套园林图纸绘制自学手册>一书中的第1章,第1.1节,作者 朱春阳 , 李晓艳 , 胡仁喜,更多章节内容可以访问云栖社区"异步社区" ...

  6. 【数据结构总结】第一章:数据结构基本概念

    [数据结构总结]第一章:数据结构基本概念 本文主要是以思维导图的形式概括数据结构第一章的精华内容,基本不会用到文字性的内容,目的是为了给大家梳理每个重要的知识点的相关概念,方便大家在复盘的时候快速阅读 ...

  7. 最优化课堂笔记01: 第一章 最优化的基本概念

    第一章  最优化的基本概念 1.最优化求解的数学模型建立 2.例题(考试第一大题:数学模型建立) 解析:优化变量.目标函数(一般取最小化).约束条件 注意: 1)约束条件一般形式为:左边为含决策变量的 ...

  8. 《深入理解分布式事务》第一章 事务的基本概念

    <深入理解分布式事务>第一章 事务的基本概念 文章目录 <深入理解分布式事务>第一章 事务的基本概念 一.事务的特性 1.原子性 2.一致性 3.隔离性 4.持久性 二.事务的 ...

  9. 【山外笔记-计算机网络·第7版】第10章:计算机网络重要概念

    本文下载地址: [学习笔记]第10章_计算机网络重要概念.pdf 教材:<计算机网络·第7版> 作者:谢希仁 时间:2020.04.17 第01章:计算机网络概述 1.计算机网络(可简称为 ...

最新文章

  1. Dubbo作者亲述:那些辉煌、沉寂与重生的故事
  2. Java多线程(二):Callable和FutureTask结合使用获取返回值
  3. matlab求解复数方程组,【求解】matlab求解非齐次方程组,但是系数矩阵是复数,求帮忙...
  4. 严重: A child container failed during start
  5. javascript校验2
  6. 一种被国人漠视的精神---狼的精神
  7. 07.配置日志的存储路径、设置日志的格式
  8. windows下JDK环境配置与Android SDK环境配置
  9. SpringBoot + Mybatis 多模块( module )项目搭建教程
  10. (附源码)计算机毕业设计ssm高校学科竞赛管理系统
  11. vue 接口请求下载文件
  12. 【码农学编曲】吉他伴奏
  13. oracle ins ctx.mk,安装Oracle10g遭遇ins_ctx.mk问题解决方法
  14. 字符26进制 与 10进制【可以这样来理解】
  15. 金融基础知识笔记(一)
  16. 全桥电路与半桥电路如何连接在一起
  17. 两种敏捷开发方式的工作流介绍
  18. Winrar制作自解压安装程序
  19. java 判断文件是否pdf_如何确定文件是否为PDF文件?
  20. 【上古秘籍】之Eclipse的秘籍

热门文章

  1. 第一篇:瑞吉外卖项目概述
  2. r语言做绘制精美pcoa图_如何绘制精美的PCoA图形
  3. 微信读书分享群+无限卡攻略
  4. 电脑贴的标签 MFG YR是什么意思
  5. JVM的一些总结(面试须知)
  6. SIP协议详解(中文)-6
  7. 怎么绕过PHP的防护,PHP代码层防护与绕过
  8. 惠普ZBook 14u G5(3XG37PA)电脑 Hackintosh 黑苹果efi引导文件
  9. 所有iOS设备的屏幕分辨率
  10. 苹果为小学生推出编程指南