演进式例解控制反转(IoC)、依赖注入(DI)之二

简介:
回顾:

上一篇文章演进式的问题描述、解决方法只有 3 个阶段,其中后面 2 个分别是引入了 Container Service Locator 这样一种间接层,以便解决各个‘问题描述’中可能的不足之处(仅仅是‘可能’,或许系统不需要考虑这么麻烦的需求,是否因为引入间接层而增大系统不必要的复杂度得由具体需求所决定),也就是希望消除(或者说转移、减弱)一些直接依赖、紧耦合。 

实际上一篇还未能引入 IoC DI,以其做铺垫热身之后的这篇才是重点要理解的。

 
 
问题描述:

然而,不管是引入 Container 还是使用 Service Locator ReportService 对于具体组件的查找、创建的方式都是‘主动’的,这意味着作为客户的 ReportService 必须清楚自己需要的是什么、到哪里获取、如何获取。一下子就因为 WhatWhereHow 而不得不增加了具体逻辑细节。 

 

例如,在前面‘引入Container ’的实现方法中,有如下代码: 

class ReportService {

    // 消除紧耦合关系,由容器取而代之

    // private static ReportGenerator generator = new PDFGenerator();

    // 通过 Container..getBean("reportGenerator") 主动查找

    private ReportGenerator generator = (ReportGenerator) Container

           .getBean("reportGenerator");

 

在‘引入 Service Locator 的实现方法中,有如下代码: 

class ServiceLocator {

    privatestatic Container container = new Container();   

    publicstatic ReportGenerator getReportGenerator() {

       // 还是container.getBean(), 用了委托而已

       return (ReportGenerator) container.getBean("reportGeneraator");

    }

} 

class ReportService {

    // ReportService 最终还是主动查找,委托给ServiceLocator 而已

    private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator();  

}

 
解决方案:

在这种情况下,变‘主动’为‘被动’无疑能够减少 ReportService 的内部知识(即查找组件的逻辑)。根据控制反转(IoC)原则,可江此种Pull,主动的)转化成Push,被动的)的模式。

 

例如,平时使用的 RSS 订阅就是Push的应用,省去了我们一天好几次登录自己喜爱的站点主动获取文章更新的麻烦。

 

而依赖注入(DI)则是实现这种被动接收、减少客户(在这里即ReportService)自身包含复杂逻辑、知晓过多的弊病。

 
实现方法:

因为我们希望是‘被动’的接收,故还是回到 Container 的例子,而不使用 Service Locator 模式。由此得到修改后的类图如下:

 
而原来的类图如下,可以对照着看一下,注意注释的提示:

 
代码实现:
为了使例子能够编译、运行,并且稍微利用跟踪代码的运行结果来显式整个类图实例化、互相协作的先后顺序,在各个类的构造器中加入了不少已编号的打印语句,以及两个无关紧要的类,有点啰唆,具体如下: 

import java.util.Date;

import java.util.HashMap;

import java.util.Map; 

// 为了能够编译运行,多了两个无关紧要的类

class Month { }

class Table {

    publicvoid setDate(Date date) {   }

    publicvoid setMonth(Month month) {    }

} 

// ------------ 以下均无甚重要改变 ----------------- //

interface ReportGenerator {

    publicvoid generate(Table table);

} 

class ExcelGenerator implements ReportGenerator { 

    public ExcelGenerator() {

       System.out.println("2...开始初始化 ExcelGenerator ...");

    } 

    publicvoid generate(Table table) {

       System.out.println("generate an Excel report ...");

    }

} 

class PDFGenerator implements ReportGenerator { 

    public PDFGenerator() {

       System.out.println("2...开始初始化 PDFGenerator ...");

    } 

    publicvoid generate(Table table) {

       System.out.println("generate an PDF report ...");

    }

}

//------------ 以上均无甚重要改变 ----------------- // 

class Container {

    // 以键-值对形式保存各种所需组件 Bean

    privatestatic Map<String, Object> beans; 

    public Container() {

       System.out.println("1...开始初始化 Container ..."); 

       beans = new HashMap<String, Object>(); 

       // 创建、保存具体的报表生起器

       ReportGenerator reportGenerator = new PDFGenerator();

       beans.put("reportGenerator", reportGenerator); 

       // 获取、管理 ReportService 的引用

       ReportService reportService = new ReportService();

       // 注入上面已创建的具体 ReportGenerator 实例

       reportService.setReportGenerator(reportGenerator);

       beans.put("reportService", reportService); 

       System.out.println("5...结束初始化 Container ...");

    } 

    publicstatic Object getBean(String id) {

       System.out.println("最后获取服务组件...getBean() --> " + id + " ...");

       returnbeans.get(id);

    }

}

 

class ReportService {   

    // private static ReportGenerator generator = new PDFGenerator();

    // 消除上面的紧耦合关系,由容器取而代之

    // private ReportGenerator generator = (ReportGenerator) Container

    //         .getBean("reportGenerator");   

    // 去除上面的主动查找,提供私有字段来保存外部注入的对象

    private ReportGenerator generator;   

    //  setter 方式从外部注入

    publicvoid setReportGenerator(ReportGenerator generator) {

       System.out.println("4...开始注入 ReportGenerator ...");

       this.generator = generator;

    }

 

    private Table table = new Table(); 

    public ReportService() {

       System.out.println("3...开始初始化 ReportService ...");

    } 

    publicvoid getDailyReport(Date date) {

       table.setDate(date);

       generator.generate(table);

    } 

    publicvoid getMonthlyReport(Month month) {

       table.setMonth(month);

       generator.generate(table);

    }

}

 

publicclass Client {

    publicstaticvoid main(String[] args) {

       // 初始化容器

       new Container();

       ReportService reportService = (ReportService) Container

              .getBean("reportService");

       reportService.getDailyReport(new Date());

       // reportService.getMonthlyReport(new Date());

    }

}

 
运行结果:

1...开始初始化 Container ...

2...开始初始化 PDFGenerator ...

3...开始初始化 ReportService ...

4...开始注入 ReportGenerator ...

5...结束初始化 Container ...

最后获取服务组件 ...getBean() --> reportService ...

generate an PDF report ...

 

注意:

1、根据上面运行结果的打印顺序,可见代码中加入的具体编号是合理的,模拟了程序执行的流程,于是也就不再画序列图了。

2、注意该例子中对IoCDI的使用,是以ReportService为客户端(即组件需求者)为基点的,而代码中的Client main()中的测试代码才是服务组件的最终用户,但它需要的不是组件,而是组件所具有的服务

3、实际在Spring框剪中,初始化Container显然不是最终用户Client应该做的事情,它应该由服务提供方事先启动就绪。

4、在最终用户Client中,我们还是用到Container.getBean("reportService")来获取事先已在Container的构造函数中实例化好的服务组件。而在具体应用中,通常是XML等配置文件将可用的服务组件部署到服务器中,再由Container读取该配置文件结合反射技术得以创建、注入具体的服务组件。

 

分析:

之前是由ReportService主动从Container中请求获取服务组件,而现在是被动地等待Container注入(Inject,也就是Push)服务组件。控制权明显地由底层模块(ReportService 是组件需求者)转移给高层模块(Container 是组件提供者),也就是控制反转了。

 

回头看看上一篇文章吧:-D,应该更能帮助理清例子的演进历程。

演进式例解控制反转(IoC)、依赖注入(DI)之

 

其他2种依赖注入方式:

上面用到的是setter方式的依赖注入,还有constructor方式的构造器注入、接口注入。

1constructor 方式

setter 方式很类似,只不过有所差异,例如:如果有过多组件需要注入,constructor方式则会造成参数列表过长;也比较僵化,因为该注入只发生在构造期,而setter 方式或者比较灵活些,需要时则注入。

 

2、接口方式

据说该方式的注入不常用,一些IoC框架如Spring也不怎么支持,问题在于其真的是比较麻烦:定义特定interface,并声明所需接口(即待实现的Method),最后组件类通过实现该interface 中的特定Method 进行组件依赖注入。既然少用,也不给出代码了。

 

小结:

感觉按照着逐步演进的步骤来理解一个问题的出现、分析原因、解决、分析结果是比较容易接收的,你觉得呢?


本文转自 xxxx66yyyy 51CTO博客,原文链接:http://blog.51cto.com/haolloyin/460631,如需转载请自行联系原作者
相关文章
|
2月前
|
XML Java 数据格式
从六个方面读懂IoC(控制反转)和DI(依赖注入)
在一开始学习 Spring 的时候,我们就接触 IoC 了,作为 Spring 第一个最核心的概念,我们在解读它源码之前一定需要对其有深入的认识,对于初学Spring的人来说,总觉得IOC是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring IOC的理解。
23 2
|
4月前
|
XML Java 数据格式
深入理解 Spring IoC 和 DI:掌握控制反转和依赖注入的精髓
在本文中,我们将介绍 IoC(控制反转)和 DI(依赖注入)的概念,以及如何在 Spring 框架中实现它们。
64 0
|
6月前
|
XML Java 应用服务中间件
面试官问我咋实现Spring框架IOC和DI好吧打趴下,深度解析手动实现Spring框架的IOC与DI功能
面试官问我咋实现Spring框架IOC和DI好吧打趴下,深度解析手动实现Spring框架的IOC与DI功能
46 0
|
26天前
|
容器
02_IOC控制反转 DI依赖注入
02_IOC控制反转 DI依赖注入
27 0
|
6月前
|
Java Spring 容器
面试官问我咋实现Spring框架IOC和DI好吧打趴下,深度解析手动实现Spring框架的IOC与DI功能2
面试官问我咋实现Spring框架IOC和DI好吧打趴下,深度解析手动实现Spring框架的IOC与DI功能2
25 0
|
3月前
|
容器
IOC 控制反转和DI依赖注入
IOC 控制反转和DI依赖注入
|
5月前
|
Java 容器 Spring
[javaweb]——spring框架之控制反转(IOC)与依赖注入(DI)
[javaweb]——spring框架之控制反转(IOC)与依赖注入(DI)
|
9月前
|
XML 图形学 数据格式
IOC控制反转——基础概念与实例
IOC控制反转——基础概念与实例
IOC控制反转 + DI依赖注入
一种思想,两种实现方式 IOC (Inversion of Control):控制反转,是一种概念和思想,指由Spring容器完成对象创建和依赖注入 核心业务:(a)对象的创建 (b)依赖的注入 2种实现方式 基于xml实现IOC 基于注解实现IOC 基于xml的IOC在前3篇Spring博客中简单探讨过了,后面将探讨基于注解的IOC
|
XML Java 程序员
Spring的艺术(二):控制反转(IOC)和依赖注入(DI)的完美实现
IOC叫做控制反转,从本质上讲,IOC就是把原本由程序员创建的对象的这个动作交给Spring去实现,程序员无需再去管理对象的创建,这种方式可以大大减少系统的偶尔性。 没有IOC之前,对象的创建和对象间的依赖关系都完全编码在程序中,使用IOC之后,对象的创建由程序自己控制,使得程序解耦合。 IOC并不是一种技术,他是一种思想,即控制权反转的思想,DI(依赖注入)则是Spring实现IOC的方法。 Spring容器在初始化时根据配置文件或元数据创建和组织对象存入容器中,需要使用时再从IOC容器中获取。

热门文章

最新文章