近来总是接触到 IoC(Inversion of Control,控制反转)、DI(Dependency Injection,依赖注入)等编程原则或者模式,而这些是著名 Java 框架 Spring、Struts 等的核心所在。针对此查了 Wikipedia 中各个条目,并从图书馆借来相关书籍,阅读后有些理解,现结合书中的讲解以及自己的加工整理如下:
开发一个能够按照不同要求生成Excel或 PDF 格式的报表的系统,例如日报表、月报表等等。
根据“面向接口编程”的原则,应该分离接口与实现,即将生成报表的功能提取为一个通用接口ReportGenerator,并提供生成 Excel 和 PDF格式报表的两个实现类 ExcelGenerator 和 PDFGenerator,而客户Client 再通过服务提供者 ReportService 获取相应的报表打印功能。
- interface ReportGenerator {
- public void generate(Table table);
- }
- class ExcelGenerator implements ReportGenerator {
- public void generate(Table table) {
- System.out.println("generate an Excel report ...");
- }
- }
- class PDFGenerator implements ReportGenerator {
- public void generate(Table table) {
- System.out.println("generate an PDF report ...");
- }
- }
- class ReportService {
- // 负责创建具体需要的报表生成器
- private ReportGenerator generator = new PDFGenerator();
- // private static ReportGenerator generator = new ExcelGenerator();
- public void getDailyReport(Date date) {
- table.setDate(date);
- // ...
- generator.generate(table);
- }
- public void getMonthlyReport(Month month) {
- table.setMonth(month);
- // ...
- generator.generate(table);
- }
- }
- public class Client {
- public static void main(String[] args) {
- ReportService reportService = new ReportService();
- reportService.getDailyReport(new Date());
- //reportService.getMonthlyReport(new Date());
- }
- }
如上面代码中的注释所示,具体的报表生成器由 ReportService 类内部硬编码创建,由此 ReportService 已经直接依赖于 PDFGenerator 或 ExcelGenerator ,必须消除这一明显的紧耦合关系。
引入一个中间管理者,也就是容器(Container),由其统一管理报表系统所涉及的对象(在这里是组件,我们将其称为 Bean),包括 ReportService 和各个 XXGenerator 。在这里使用一个键-值对形式的 HashMap 实例来保存这些 Bean。
- class Container {
- // 以键-值对形式保存各种所需组件 Bean
- private static Map<String, Object> beans;
- public Container() {
- beans = new HashMap<String, Object>();
- // 创建、保存具体的报表生起器
- ReportGenerator reportGenerator = new PDFGenerator();
- beans.put("reportGenerator", reportGenerator);
- // 获取、管理 ReportService 的引用
- ReportService reportService = new ReportService();
- beans.put("reportService", reportService);
- }
- public static Object getBean(String id) {
- return beans.get(id);
- }
- }
- class ReportService {
- // 消除紧耦合关系,由容器取而代之
- // private static ReportGenerator generator = new PDFGenerator();
- private ReportGenerator generator = (ReportGenerator) Container.getBean("reportGenerator");
- public void getDailyReport(Date date) {
- table.setDate(date);
- generator.generate(table);
- }
- public void getMonthlyReport(Month month) {
- table.setMonth(month);
- generator.generate(table);
- }
- }
- public class Client {
- public static void main(String[] args) {
- Container container = new Container();
- ReportService reportService = (ReportService)Container.getBean("reportService");
- reportService.getDailyReport(new Date());
- //reportService.getMonthlyReport(new Date());
- }
- }
如上面所示,ReportService 不再与具体的 ReportGenerator 直接关联,已经用容器将接口和实现隔离开来了,提高了系统组件 Bean 的重用性,此时还可以使用配置文件在 Container 中实时获取具体组件的定义。
然而,观察上面的类图,很容易发现 ReportService 与 Container 之间存在双向关联,彼此互相有依赖关系。并且,如果想要重用 ReportService,由于它也是直接依赖于单独一个 Container 的具体查找逻辑。若其他容器具体不同的组件查找机制(如 JNDI),此时重用 ReportService 意味着需要修改 Container 的内部查找逻辑。
再次引入一个间接层 Service Locator,用于提供组件查找逻辑的接口,请看Wikipedia 中的描述 或者 Java EE 对其的描述1 、描述2 。这样就能够将可能变化的点隔离开来。
- // 实际应用中可以是用 interface 来提供统一接口
- class ServiceLocator {
- private static Container container = new Container();
- public static ReportGenerator getReportGenerator() {
- return (ReportGenerator)container.getBean("reportGeneraator");
- }
- }
- class ReportService {
- private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator();
- // ...
- }
小结:
1、虽然讲了这么大篇幅还没有进入真正的主题——IoC、DI,不过已经在一步步逼近了,下一篇应该会更精彩!在这里...
2、可以很明显地看得出上面两中重新设计以解耦、隔离变化点都是通过引入间接层得以解决的。
3、在看书过程中,我感觉《Spring 攻略》一书中以“问题描述、解决方案、实现方法”方式的讲解比较容易理解和理清思路,故而也学习用这种方式来写。另,推荐该书以学习 Spring 框架(尽管目前我看得也不多)。
(Factory Method)工厂方法模式的Java实现
Java RMI 框架的工厂方法模式实现
(Mediator)中介者模式的Java实现(加修改)
(Dynamic Proxy)动态代理模式的Java实现
(Template Method)模板方法模式的Java实现