接上一篇“演进式例解控制反转(IoC)、依赖注入(DI)之一”的例子继续往下。
上一篇文章演进式的问题描述、解决方法只有 3 个阶段,其中后面 2 个分别是引入了 Container、Service Locator这样一种间接层,以便解决各个‘问题描述’中可能的不足之处(仅仅是‘可能’,或许系统不需要考虑这么麻烦的需求,是否因为引入间接层而增大系统不必要的复杂度得由具体需求所决定),也就是希望消除(或者说转移、减弱)一些直接依赖、紧耦合。
实际上一篇还未能引入 IoC 、DI,以其做铺垫热身之后的这篇才是重点要理解的。
然而,不管是引入 Container 还是使用 Service Locator ,ReportService 对于具体组件的查找、创建的方式都是‘主动’的,这意味着作为客户的 ReportService必须清楚自己需要的是什么、到哪里获取、如何获取。一下子就因为 What、Where、How而不得不增加了具体逻辑细节。
例如,在前面‘引入Container ’的实现方法中,有如下代码:
class ReportService {
//消除紧耦合关系,由容器取而代之
//private static ReportGeneratorgenerator = new PDFGenerator();
//通过Container..getBean("reportGenerator")‘主动’查找
private ReportGeneratorgenerator = (ReportGenerator) Container
.getBean("reportGenerator");
在‘引入 Service Locator’的实现方法中,有如下代码:
class ServiceLocator {
privatestaticContainercontainer =newContainer();
publicstaticReportGenerator getReportGenerator() {
//还是container.getBean(),用了委托而已
return(ReportGenerator) container.getBean("reportGeneraator");
}
}
class ReportService {
// ReportService最终还是‘主动’查找,委托给ServiceLocator而已
privateReportGeneratorreportGenerator = 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 ExcelGeneratorimplements ReportGenerator {
public ExcelGenerator() {
System.out.println("2...开始初始化 ExcelGenerator ...");
}
publicvoid generate(Table table) {
System.out.println("generate an Excel report ...");
}
}
class PDFGeneratorimplements 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>();
//创建、保存具体的报表生起器
ReportGeneratorreportGenerator =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 ReportGeneratorgenerator = new PDFGenerator();
//消除上面的紧耦合关系,由容器取而代之
//private ReportGeneratorgenerator = (ReportGenerator) Container
// .getBean("reportGenerator");
//去除上面的“主动”查找,提供私有字段来保存外部注入的对象
private ReportGenerator generator;
//以setter方式从外部注入
publicvoid setReportGenerator(ReportGenerator generator) {
System.out.println("4...开始注入 ReportGenerator ...");
this.generator = generator;
}
private Tabletable =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 ...
generate an PDF report ...
注意:
1、根据上面运行结果的打印顺序,可见代码中加入的具体编号是合理的,模拟了程序执行的流程,于是也就不再画序列图了。
2、注意该例子中对IoC、DI的使用,是以ReportService为客户端(即组件需求者)为基点的,而代码中的Client类main()中的测试代码才是服务组件的最终用户,但它需要的不是组件,而是组件所具有的服务。
3、实际在Spring框剪中,初始化Container显然不是最终用户Client应该做的事情,它应该由服务提供方事先启动就绪。
4、在最终用户Client中,我们还是用到Container.getBean("reportService")来获取事先已在Container的构造函数中实例化好的服务组件。而在具体应用中,通常是用XML等配置文件将可用的服务组件部署到服务器中,再由Container读取该配置文件结合反射技术得以创建、注入具体的服务组件。
分析:
之前是由ReportService主动从Container中请求获取服务组件,而现在是被动地等待Container注入(Inject,也就是Push)服务组件。控制权明显地由底层模块(ReportService是组件需求者)转移给高层模块(Container是组件提供者),也就是控制反转了。
回头看看上一篇文章吧:-D,应该更能帮助理清例子的演进历程。
其他2种依赖注入方式:
上面用到的是setter方式的依赖注入,还有constructor方式的构造器注入、接口注入。
1、constructor方式
与setter方式很类似,只不过有所差异,例如:如果有过多组件需要注入,constructor方式则会造成参数列表过长;也比较僵化,因为该注入只发生在构造期,而setter方式或者比较灵活些,需要时则注入。
2、接口方式
据说该方式的注入不常用,一些IoC框架如Spring也不怎么支持,问题在于其真的是比较麻烦:定义特定interface,并声明所需接口(即待实现的Method),最后组件类通过实现该interface中的特定Method进行组件依赖注入。既然少用,也不给出代码了。
小结: