接上一篇“演进式例解控制反转(IoC)、依赖注入(DI)之一”的例子继续往下。
回顾:
上一篇文章演进式的问题描述、解决方法只有3个阶段,其中后面2个分别是引入了Container、Service Locator这样一种间接层,以便解决各个‘问题描述’中可能的不足之处(仅仅是‘可能’,或许系统不需要考虑这么麻烦的需求,是否因为引入间接层而增大系统不必要的复杂度得由具体需求所决定),也就是希望消除(或者说转移、减弱)一些直接依赖、紧耦合。
实际上一篇还未能引入IoC、DI,以其做铺垫热身之后的这篇才是重点要理解的。
问题描述:
然而,不管是引入Container还是使用Service Locator,ReportService对于具体组件的查找、创建的方式都是‘主动’的,这意味着作为客户的ReportService必须清楚自己需要的是什么、到哪里获取、如何获取。一下子就因为What、Where、How而不得不增加了具体逻辑细节。
例如,在前面‘引入Container’的实现方法中,有如下代码:
classReportService {
//消除紧耦合关系,由容器取而代之
//privatestaticReportGeneratorgenerator=newPDFGenerator();
//通过Container..getBean("reportGenerator")‘主动’查找
privateReportGeneratorgenerator= (ReportGenerator) Container
.getBean("reportGenerator");
在‘引入Service Locator’的实现方法中,有如下代码:
classServiceLocator {
privatestaticContainercontainer=newContainer();
publicstaticReportGeneratorgetReportGenerator() {
//还是container.getBean(),用了委托而已
return(ReportGenerator)container.getBean("reportGeneraator");
}
}
// ReportService最终还是‘主动’查找,委托给ServiceLocator而已
privateReportGeneratorreportGenerator= ServiceLocator.getReportGenerator();
}
在这种情况下,变‘主动’为‘被动’无疑能够减少ReportService的内部知识(即查找组件的逻辑)。根据控制反转(IoC)原则,可江此种拉(Pull,主动的)转化成推(Push,被动的)的模式。
例如,平时使用的RSS订阅就是Push的应用,省去了我们一天好几次登录自己喜爱的站点主动获取文章更新的麻烦。
而依赖注入(DI)则是实现这种被动接收、减少客户(在这里即ReportService)自身包含复杂逻辑、知晓过多的弊病。
因为我们希望是‘被动’的接收,故还是回到Container的例子,而不使用Service Locator模式。由此得到修改后的类图如下:
而原来的类图如下,可以对照着看一下,注意注释的提示:
为了使例子能够编译、运行,并且稍微利用跟踪代码的运行结果来显式整个类图实例化、互相协作的先后顺序,在各个类的构造器中加入了不少已编号的打印语句,以及两个无关紧要的类,有点啰唆,具体如下:
importjava.util.Date;
importjava.util.HashMap;
importjava.util.Map;
//为了能够编译运行,多了两个无关紧要的类
classMonth { }
classTable {
voidsetDate(Date date) { }
voidsetMonth(Month month) { }
}
// ------------以下均无甚重要改变----------------- //
interfaceReportGenerator {
voidgenerate(Table table);
}
classExcelGeneratorimplementsReportGenerator {
publicExcelGenerator() {
System.out.println("2...开始初始化ExcelGenerator ...");
}
voidgenerate(Table table) {
System."generate an Excel report ...");
}
}
classPDFGeneratorpublicPDFGenerator() {
System.PDFGenerator ...");
}
"generate an PDF report ...");
}
}
//------------以上均无甚重要改变classContainer {
//以键-值对形式保存各种所需组件Bean
staticMap<String,Object>beans;
publicContainer() {
System."1...开始初始化Container ...");
beans=newHashMap<String,Object>();
//创建、保存具体的报表生起器
ReportGeneratorreportGenerator=newPDFGenerator();
beans.put("reportGenerator",reportGenerator);
//获取、管理ReportService的引用
ReportService reportService =newReportService();
//注入上面已创建的具体ReportGenerator实例
reportService.setReportGenerator(reportGenerator);
"reportService",reportService);
System."5...结束初始化Container ...");
}
staticObject getBean(String id) {
System."最后获取服务组件...getBean() --> "+ id +" ...");
returnbeans.get(id);
}
}
classReportService {
//消除上面的紧耦合关系,由容器取而代之
//privateReportGeneratorgenerator= (ReportGenerator)Container
// .getBean("reportGenerator");
//去除上面的“主动”查找,提供私有字段来保存外部注入的对象
privateReportGeneratorgenerator;
//以setter方式从外部注入
voidsetReportGenerator(ReportGenerator generator) {
System."4...开始注入ReportGenerator ...");
this.generator= generator;
}
privateTabletable=newTable();
publicReportService() {
System."3...开始初始化ReportService ...");
}
voidgetDailyReport(Date date) {
table.setDate(date);
generator.generate(table);
}
voidgetMonthlyReport(Month month) {
table.setMonth(month);
table);
}
}
classClient {
staticvoidmain(String[] args) {
//初始化容器
newContainer();
ReportService reportService = (ReportService) Container
.getBean("reportService");
reportService.getDailyReport(newDate());
// reportService.getMonthlyReport(newDate());
}
}
运行结果:
1...开始初始化Container ...
2...开始初始化PDFGenerator ...
3...开始初始化ReportService ...
4...开始注入ReportGenerator ...
5...结束初始化Container ...
最后获取服务组件
...getBean() --> reportService ...
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,应该更能帮助理清例子的演进历程。
演进式例解控制反转(IoC)、依赖注入(DI)之一
其他2种依赖注入方式:
上面用到的是setter方式的依赖注入,还有constructor方式的构造器注入、接口注入。
1、constructor方式
与setter方式很类似,只不过有所差异,例如:如果有过多组件需要注入,constructor方式则会造成参数列表过长;也比较僵化,因为该注入只发生在构造期,而setter方式或者比较灵活些,需要时则注入。
2、接口方式
据说该方式的注入不常用,一些IoC框架如Spring也不怎么支持,问题在于其真的是比较麻烦:定义特定interface,并声明所需接口(即待实现的Method),最后组件类通过实现该interface中的特定Method进行组件依赖注入。既然少用,也不给出代码了。
小结:
感觉按照着逐步演进的步骤来理解一个问题的出现、分析原因、解决、分析结果是比较容易接收的,你觉得呢?