微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

原文地址:http://haolloyin.blog.51cto.com/1177454/458416/


近来总是接触到 IoCInversion of Control,控制反转)、DIDependency Injection,依赖注入)等编程原则或者模式,而这些是著名 Java 框架 SpringStruts 等的核心所在。针对此查了Wikipedia 中各个条目,并从图书馆借来相关书籍,阅读后有些理解,现结合书中的讲解以及自己的加工整理如下:

 
问题描述:

开发一个能够按照不同要求生成Excel PDF 格式的报表的系统,例如日报表、月报表等等。

解决方案:

根据“面向接口编程”的原则,应该分离接口与实现,即将生成报表的功能提取一个通用接口ReportGenerator,并提供生成 Excel  PDF格式报表的两个实现类 ExcelGenerator PDFGenerator,而客户Client 再通过服务提供者 ReportService 获取相应的报表打印功能

根据上面所述,得到如下类图:

代码实现:
 
 
 
  1. interface ReportGenerator { 
  2.     public void generate(Table table); 
  3.  
  4. class ExcelGenerator implements ReportGenerator { 
  5.     void generate(Table table) { 
  6.         System.out.println("generate an Excel report ..."); 
  7.     } 
  8.  
  9. class PDFGenerator void generate(Table table) { 
  10.         System.out.println("generate an PDF report ..."); 
  11.     } 
  12.  
  13. class ReportService { 
  14.     // 负责创建具体需要的报表生成 
  15.     private ReportGenerator generator = new PDFGenerator()
  16.     // private static ReportGenerator generator = new ExcelGenerator(); 
  17.      
  18.     void getDailyReport(Date date) { 
  19.         table.setDate(date); 
  20.         // ... 
  21.         generator.generate(table); 
  22.     } 
  23.      
  24.     void getMonthlyReport(Month month) { 
  25.         table.setMonth(month); 
  26.         // ... 
  27.         generator.generate(table); 
  28.     } 
  29.  
  30.  
  31. class Client { 
  32.     static void main(String[] args) { 
  33.         ReportService reportService = new ReportService(); 
  34.         reportService.getDailyReport(new Date()); 
  35.         //reportService.getMonthlyReport(new Date()); 
  36.     } 
 
问题描述:

如上面代码中的注释所示,具体的报表生成器由 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); 
  •     } 
  •      
  •     static Object getBean(String id) { 
  •         return beans.get(id); 
  •     } 
  •  
  • class ReportService { 
  •     // 消除紧耦合关系,由容器取而代之 
  •     // private static ReportGenerator generator = new PDFGenerator(); 
  •     private ReportGenerator generator = (ReportGenerator) Container.getBean("reportGenerator"); 
  •  
  •     void getDailyReport(Date date) { 
  •         table.setDate(date); 
  •         generator.generate(table); 
  •     } 
  •      
  •     void getMonthlyReport(Month month) { 
  •         table.setMonth(month); 
  •         generator.generate(table); 
  •     } 
  •  
  • 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

    再次引入一个间接层 Service Locator,用于提供组件查找逻辑的接口,请看Wikipedia 中的描述 或者 Java EE 对其的描述1 描述2 。这样就能够将可能变化的点隔离开来。

    类图如下:

    代码实现:
  • // 实际应用中可以是用 interface 来提供统一接口 
  • class ServiceLocator { 
  •     static Container container = new Container(); 
  •      
  •     static ReportGenerator getReportGenerator() { 
  •         return (ReportGenerator)container.getBean("reportGeneraator"); 
  •     } 
  •  
  • class ReportService { 
  •     private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator(); 
  •      
  •     // ... 

  •  

    小结:

    1、虽然讲了这么大篇幅还没有进入真正的主题——IoCDI,不过已经在一步步逼近了,下一篇应该会更精彩!在这里...

    2、可以很明显地看得出上面两中重新设计以解耦、隔离变化点都是通过引入间接层得以解决的。

    3、在看书过程中,我感觉《Spring 攻略》一书中以“问题描述、解决方案、实现方法”方式的讲解比较容易理解和理清思路,故而也学习用这种方式来写。另,推荐该书以学习 Spring 框架(尽管目前我看得也不多)。

    版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

    相关推荐