近來總是接觸到 IoC(Inversion of Control,控制反轉(zhuǎn))、DI(Dependency Injection,依賴注入)等編程原則或者模式,而這些是著名 Java 框架 Spring、Struts 等的核心所在。針對此查了 Wikipedia 中各個條目,并從圖書館借來相關(guān)書籍,閱讀后有些理解,現(xiàn)結(jié)合書中的講解以及自己的加工整理如下:
eg1
問題描述:
開發(fā)一個能夠按照不同要求生成Excel或 PDF 格式的報表的系統(tǒng),例如日報表、月報表等等。
解決方案:
根據(jù)“面向接口編程”的原則,應(yīng)該分離接口與實現(xiàn),即將生成報表的功能提取為一個通用接口ReportGenerator,并提供生成 Excel 和 PDF格式報表的兩個實現(xiàn)類 ExcelGenerator 和 PDFGenerator,而客戶Client 再通過服務(wù)提供者 ReportService 獲取相應(yīng)的報表打印功能。
實現(xiàn)方法:
根據(jù)上面所述,得到如下類圖:

代碼實現(xiàn):
01 | interface ReportGenerator { |
02 | public void generate(Table table); |
05 | class ExcelGenerator implements ReportGenerator { |
06 | public void generate(Table table) { |
07 | System.out.println( "generate an Excel report ..." ); |
11 | class PDFGenerator implements ReportGenerator { |
12 | public void generate(Table table) { |
13 | System.out.println( "generate an PDF report ..." ); |
19 | private ReportGenerator generator = new PDFGenerator(); |
22 | public void getDailyReport(Date date) { |
25 | generator.generate(table); |
28 | public void getMonthlyReport(Month month) { |
29 | table.setMonth(month); |
31 | generator.generate(table); |
37 | public static void main(String[] args) { |
38 | ReportService reportService = new ReportService(); |
39 | reportService.getDailyReport( new Date()); |
eg2
問題描述:
如上面代碼中的注釋所示,具體的報表生成器由 ReportService 類內(nèi)部硬編碼創(chuàng)建,由此 ReportService 已經(jīng)直接依賴于 PDFGenerator 或 ExcelGenerator ,必須消除這一明顯的緊耦合關(guān)系。
解決方案:引入容器
引入一個中間管理者,也就是容器(Container),由其統(tǒng)一管理報表系統(tǒng)所涉及的對象(在這里是組件,我們將其稱為 Bean),包括 ReportService 和各個 XXGenerator 。在這里使用一個鍵-值對形式的 HashMap 實例來保存這些 Bean。
實現(xiàn)方法:
得到類圖如下:

代碼實現(xiàn):
03 | private static Map<String, Object> beans; |
06 | beans = new HashMap<String, Object>(); |
09 | ReportGenerator reportGenerator = new PDFGenerator(); |
10 | beans.put( "reportGenerator" , reportGenerator); |
13 | ReportService reportService = new ReportService(); |
14 | beans.put( "reportService" , reportService); |
17 | public static Object getBean(String id) { |
25 | private ReportGenerator generator = (ReportGenerator) Container.getBean( "reportGenerator" ); |
27 | public void getDailyReport(Date date) { |
29 | generator.generate(table); |
32 | public void getMonthlyReport(Month month) { |
33 | table.setMonth(month); |
34 | generator.generate(table); |
39 | public static void main(String[] args) { |
40 | Container container = new Container(); |
41 | ReportService reportService = (ReportService)Container.getBean( "reportService" ); |
42 | reportService.getDailyReport( new Date()); |
時序圖大致如下:

效果:
如上面所示,ReportService 不再與具體的 ReportGenerator 直接關(guān)聯(lián),已經(jīng)用容器將接口和實現(xiàn)隔離開來了,提高了系統(tǒng)組件 Bean 的重用性,此時還可以使用配置文件在 Container 中實時獲取具體組件的定義。
eg3
問題描述:
然而,觀察上面的類圖,很容易發(fā)現(xiàn) ReportService 與 Container 之間存在雙向關(guān)聯(lián),彼此互相有依賴關(guān)系。并且,如果想要重用 ReportService,由于它也是直接依賴于單獨一個 Container 的具體查找邏輯。若其他容器具體不同的組件查找機制(如 JNDI),此時重用 ReportService 意味著需要修改 Container 的內(nèi)部查找邏輯。
解決方案:引入 Service Locator
再次引入一個間接層 Service Locator,用于提供組件查找邏輯的接口,請看Wikipedia 中的描述 或者 Java EE 對其的描述1 、描述2 。這樣就能夠?qū)⒖赡茏兓狞c隔離開來。
實現(xiàn)方法:
類圖如下:

代碼實現(xiàn):
03 | private static Container container = new Container(); |
05 | public static ReportGenerator getReportGenerator() { |
06 | return (ReportGenerator)container.getBean( "reportGeneraator" ); |
11 | private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator(); |
eg4
問題描述:
然而,不管是引入 Container 還是使用 Service Locator ,ReportService 對于具體組件的查找、創(chuàng)建的方式都是‘主動'的,這意味著作為客戶的 ReportService 必須清楚自己需要的是什么、到哪里獲取、如何獲取。一下子就因為 What、Where、How 而不得不增加了具體邏輯細節(jié)。
例如,在前面‘引入Container '的實現(xiàn)方法中,有如下代碼:
5 | private ReportGenerator generator = (ReportGenerator) Container |
6 | .getBean( "reportGenerator" ); |
在‘引入 Service Locator '的實現(xiàn)方法中,有如下代碼:
02 | privatestatic Container container = new Container(); |
03 | publicstatic ReportGenerator getReportGenerator() { |
05 | return (ReportGenerator) container.getBean( "reportGeneraator" ); |
10 | private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator(); |
解決方案:
在這種情況下,變‘主動'為‘被動'無疑能夠減少 ReportService 的內(nèi)部知識(即查找組件的邏輯)。根據(jù)控制反轉(zhuǎn)(IoC)原則,可江此種拉(Pull,主動的)轉(zhuǎn)化成推(Push,被動的)的模式。
例如,平時使用的 RSS 訂閱就是Push的應(yīng)用,省去了我們一天好幾次登錄自己喜愛的站點主動獲取文章更新的麻煩。
而依賴注入(DI)則是實現(xiàn)這種被動接收、減少客戶(在這里即ReportService)自身包含復(fù)雜邏輯、知曉過多的弊病。
實現(xiàn)方法:
因為我們希望是‘被動'的接收,故還是回到 Container 的例子,而不使用 Service Locator 模式。由此得到修改后的類圖如下:

而原來的類圖如下,可以對照著看一下,注意注釋的提示:

代碼實現(xiàn):
為了使例子能夠編譯、運行,并且稍微利用跟蹤代碼的運行結(jié)果來顯式整個類圖實例化、互相協(xié)作的先后順序,在各個類的構(gòu)造器中加入了不少已編號的打印語句,以及兩個無關(guān)緊要的類,有點啰唆,具體如下:
02 | import java.util.HashMap; |
07 | publicvoid setDate(Date date) { } |
08 | publicvoid setMonth(Month month) { } |
11 | interface ReportGenerator { |
12 | publicvoid generate(Table table); |
14 | class ExcelGenerator implements ReportGenerator { |
15 | public ExcelGenerator() { |
16 | System.out.println( "2...開始初始化 ExcelGenerator ..." ); |
18 | publicvoid generate(Table table) { |
19 | System.out.println( "generate an Excel report ..." ); |
22 | class PDFGenerator implements ReportGenerator { |
23 | public PDFGenerator() { |
24 | System.out.println( "2...開始初始化 PDFGenerator ..." ); |
26 | publicvoid generate(Table table) { |
27 | System.out.println( "generate an PDF report ..." ); |
33 | privatestatic Map<String, Object> beans; |
35 | System.out.println( "1...開始初始化 Container ..." ); |
36 | beans = new HashMap<String, Object>(); |
38 | ReportGenerator reportGenerator = new PDFGenerator(); |
39 | beans.put( "reportGenerator" , reportGenerator); |
41 | ReportService reportService = new ReportService(); |
43 | reportService.setReportGenerator(reportGenerator); |
44 | beans.put( "reportService" , reportService); |
45 | System.out.println( "5...結(jié)束初始化 Container ..." ); |
47 | publicstatic Object getBean(String id) { |
48 | System.out.println( "最后獲取服務(wù)組件...getBean() --> " + id + " ..." ); |
59 | private ReportGenerator generator; |
61 | publicvoid setReportGenerator(ReportGenerator generator) { |
62 | System.out.println( "4...開始注入 ReportGenerator ..." ); |
63 | this .generator = generator; |
66 | private Table table = new Table(); |
67 | public ReportService() { |
68 | System.out.println( "3...開始初始化 ReportService ..." ); |
70 | publicvoid getDailyReport(Date date) { |
72 | generator.generate(table); |
74 | publicvoid getMonthlyReport(Month month) { |
75 | table.setMonth(month); |
76 | generator.generate(table); |
81 | publicstaticvoid main(String[] args) { |
84 | ReportService reportService = (ReportService) Container |
85 | .getBean( "reportService" ); |
86 | reportService.getDailyReport( new Date()); |
運行結(jié)果:
2 | 2...開始初始化 PDFGenerator ... |
3 | 3...開始初始化 ReportService ... |
4 | 4...開始注入 ReportGenerator ... |
5 | 5...結(jié)束初始化 Container ... |
7 | 最后獲取服務(wù)組件...getBean() --> reportService ... |
8 | generate an PDF report ... |
注意:
1、根據(jù)上面運行結(jié)果的打印順序,可見代碼中加入的具體編號是合理的,模擬了程序執(zhí)行的流程,于是也就不再畫序列圖了。
2、注意該例子中對IoC、DI的使用,是以ReportService為客戶端(即組件需求者)為基點的,而代碼中的Client 類main()中的測試代碼才是服務(wù)組件的最終用戶,但它需要的不是組件,而是組件所具有的服務(wù)。
3、實際在Spring框剪中,初始化Container顯然不是最終用戶Client應(yīng)該做的事情,它應(yīng)該由服務(wù)提供方事先啟動就緒。
4、在最終用戶Client中,我們還是用到Container.getBean("reportService")來獲取事先已在Container的構(gòu)造函數(shù)中實例化好的服務(wù)組件。而在具體應(yīng)用中,通常是用XML等配置文件將可用的服務(wù)組件部署到服務(wù)器中,再由Container讀取該配置文件結(jié)合反射技術(shù)得以創(chuàng)建、注入具體的服務(wù)組件。
分析:
之前是由ReportService主動從Container中請求獲取服務(wù)組件,而現(xiàn)在是被動地等待Container注入(Inject,也就是Push)服務(wù)組件。控制權(quán)明顯地由底層模塊(ReportService 是組件需求者)轉(zhuǎn)移給高層模塊(Container 是組件提供者),也就是控制反轉(zhuǎn)了。