近來總是接觸到 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 框架(儘管目前我看得也不多)。
(Template Method)模板方法模式的Java實現