演進式例解控制反轉(IoC)、依賴注入(DI)之一

 近來總是接觸到 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.     public void generate(Table table) { 
  6.         System.out.println("generate an Excel report ..."); 
  7.     } 
  8.  
  9. class PDFGenerator implements ReportGenerator { 
  10.     public void generate(Table table) { 
  11.         System.out.println("generate an PDF report ..."); 
  12.     } 
  13.  
  14. class ReportService { 
  15.     // 負責創建具體需要的報表生成器 
  16.     private ReportGenerator generator = new PDFGenerator()
  17.     // private static ReportGenerator generator = new ExcelGenerator(); 
  18.      
  19.     public void getDailyReport(Date date) { 
  20.         table.setDate(date); 
  21.         // ... 
  22.         generator.generate(table); 
  23.     } 
  24.      
  25.     public void getMonthlyReport(Month month) { 
  26.         table.setMonth(month); 
  27.         // ... 
  28.         generator.generate(table); 
  29.     } 
  30.  
  31.  
  32. public class Client { 
  33.     public static void main(String[] args) { 
  34.         ReportService reportService = new ReportService(); 
  35.         reportService.getDailyReport(new Date()); 
  36.         //reportService.getMonthlyReport(new Date()); 
  37.     } 
 
 
問題描述:

如上面代碼中的註釋所示,具體的報表生成器由 ReportService 類內部硬編碼創建,由此 ReportService 已經直接依賴 PDFGenerator ExcelGenerator ,必須消除這一明顯的緊耦合關係。

 
解決方案:引入容器

引入一箇中間管理者,也就是容器(Container),由其統一管理報表系統所涉及的對象(在這裏是組件,我們將其稱爲 Bean),包括 ReportService 和各個 XXGenerator 。在這裏使用一個鍵-值對形式的 HashMap 實例來保存這些 Bean

 
實現方法:
得到類圖如下:

 
代碼實現:
  1. class Container { 
  2.     // 以鍵-值對形式保存各種所需組件 Bean 
  3.     private static Map<String, Object> beans
  4.      
  5.     public Container() { 
  6.         beans = new HashMap<String, Object>(); 
  7.          
  8.         // 創建、保存具體的報表生起器 
  9.         ReportGenerator reportGenerator = new PDFGenerator()
  10.         beans.put("reportGenerator", reportGenerator); 
  11.          
  12.         // 獲取、管理 ReportService 的引用 
  13.         ReportService reportService = new ReportService(); 
  14.         beans.put("reportService", reportService); 
  15.     } 
  16.      
  17.     public static Object getBean(String id) { 
  18.         return beans.get(id); 
  19.     } 
  20.  
  21. class ReportService { 
  22.     // 消除緊耦合關係,由容器取而代之 
  23.     // private static ReportGenerator generator = new PDFGenerator(); 
  24.     private ReportGenerator generator = (ReportGenerator) Container.getBean("reportGenerator"); 
  25.  
  26.     public void getDailyReport(Date date) { 
  27.         table.setDate(date); 
  28.         generator.generate(table); 
  29.     } 
  30.      
  31.     public void getMonthlyReport(Month month) { 
  32.         table.setMonth(month); 
  33.         generator.generate(table); 
  34.     } 
  35.  
  36. public class Client { 
  37.     public static void main(String[] args) { 
  38.         Container container = new Container(); 
  39.         ReportService reportService = (ReportService)Container.getBean("reportService"); 
  40.         reportService.getDailyReport(new Date()); 
  41.         //reportService.getMonthlyReport(new Date()); 
  42.     } 
 
時序圖大致如下:

 
效果:

如上面所示,ReportService 不再與具體的 ReportGenerator 直接關聯,已經用容器將接口和實現隔離開來了,提高了系統組件 Bean 的重用性,此時還可以使用配置文件在 Container 中實時獲取具體組件的定義。

 
 
問題描述:

然而,觀察上面的類圖,很容易發現 ReportService Container 之間存在雙向關聯,彼此互相有依賴關係。並且,如果想要重用 ReportService,由於它也是直接依賴於單獨一個 Container 具體查找邏輯。若其他容器具體不同的組件查找機制(如 JNDI),此時重用 ReportService 意味着需要修改 Container 的內部查找邏輯。

 
解決方案:引入 Service Locator

再次引入一個間接層 Service Locator,用於提供組件查找邏輯的接口,請看Wikipedia 中的描述 或者 Java EE 對其的描述1 描述2 。這樣就能夠將可能變化的點隔離開來。

 
實現方法:
類圖如下:

 
代碼實現:
 
  1. // 實際應用中可以是用 interface 來提供統一接口 
  2. class ServiceLocator { 
  3.     private static Container container = new Container(); 
  4.      
  5.     public static ReportGenerator getReportGenerator() { 
  6.         return (ReportGenerator)container.getBean("reportGeneraator"); 
  7.     } 
  8.  
  9. class ReportService { 
  10.     private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator(); 
  11.      
  12.     // ... 


 

小結:

1、雖然講了這麼大篇幅還沒有進入真正的主題——IoCDI,不過已經在一步步逼近了,下一篇應該會更精彩!在這裏...

2、可以很明顯地看得出上面兩中重新設計以解耦、隔離變化點都是通過引入間接層得以解決的。

3、在看書過程中,我感覺《Spring 攻略》一書中以“問題描述、解決方案、實現方法”方式的講解比較容易理解和理清思路,故而也學習用這種方式來寫。另,推薦該書以學習 Spring 框架(儘管目前我看得也不多)。

 
以下文章你可能也會感興趣:

Factory Method)工廠方法模式的Java實現

Java RMI 框架的工廠方法模式實現

Mediator)中介者模式的Java實現(加修改)

Dynamic Proxy)動態代理模式的Java實現

Template Method)模板方法模式的Java實現

  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章