接上一篇「演進式例解控制反轉(IoC)、依賴注入(DI)之一」的例子繼續往下。java
回顧:
上一篇文章演進式的問題描述、解決方法只有 3 個階段,其中後面 2 個分別是引入了 Container、Service Locator 這樣一種間接層,以便解決各個‘問題描述’中可能的不足之處(僅僅是‘可能’,或許系統不須要考慮這麼麻煩的需求,是否由於引入間接層而增大系統沒必要要的複雜度得由具體需求所決定),也就是但願消除(或者說轉移、減弱)一些直接依賴、緊耦合。 服務器
實際上一篇還未能引入 IoC 、DI,以其作鋪墊熱身以後的這篇纔是重點要理解的。框架
問題描述:
然而,不論是引入 Container 仍是使用 Service Locator ,ReportService 對於具體組件的查找、建立的方式都是‘主動’的,這意味着做爲客戶的 ReportService 必須清楚本身須要的是什麼、到哪裏獲取、如何獲取。一會兒就由於 What、Where、How 而不得不增長了具體邏輯細節。 函數
例如,在前面‘引入Container ’的實現方法中,有以下代碼: 測試
class ReportService {this
// 消除緊耦合關係,由容器取而代之spa
// private static ReportGenerator generator = new PDFGenerator();對象
// 經過 Container..getBean("reportGenerator") ‘主動’查找blog
private ReportGenerator generator = (ReportGenerator) Container接口
.getBean("reportGenerator");
在‘引入 Service Locator ’的實現方法中,有以下代碼:
class ServiceLocator {
privatestatic Container container = new Container();
publicstatic ReportGenerator getReportGenerator() {
// 仍是container.getBean(), 用了委託而已
return (ReportGenerator) container.getBean("reportGeneraator");
}
}
class ReportService {
// ReportService 最終仍是‘主動’查找,委託給ServiceLocator 而已
private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator();
}
解決方案:
在這種狀況下,變‘主動’爲‘被動’無疑可以減小 ReportService 的內部知識(即查找組件的邏輯)。根據控制反轉(IoC)原則,可江此種拉(Pull,主動的)轉化成推(Push,被動的)的模式。
例如,平時使用的 RSS 訂閱就是Push的應用,省去了咱們一天好幾回登陸本身喜好的站點主動獲取文章更新的麻煩。
而依賴注入(DI)則是實現這種被動接收、減小客戶(在這裏即ReportService)自身包含複雜邏輯、知曉過多的弊病。
實現方法:
由於咱們但願是‘被動’的接收,故仍是回到 Container 的例子,而不使用 Service Locator 模式。由此獲得修改後的類圖以下:
而原來的類圖以下,能夠對照着看一下,注意註釋的提示:
代碼實現:
爲了使例子可以編譯、運行,而且稍微利用跟蹤代碼的運行結果來顯式整個類圖實例化、互相協做的前後順序,在各個類的構造器中加入了很多已編號的打印語句,以及兩個可有可無的類,有點囉唆,具體以下:
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
// 爲了可以編譯運行,多了兩個可有可無的類
class Month { }
class Table {
publicvoid setDate(Date date) { }
publicvoid setMonth(Month month) { }
}
// ------------ 如下均無甚重要改變 ----------------- //
interface ReportGenerator {
publicvoid generate(Table table);
}
class ExcelGenerator implements ReportGenerator {
public ExcelGenerator() {
System.out.println("2...開始初始化 ExcelGenerator ...");
}
publicvoid generate(Table table) {
System.out.println("generate an Excel report ...");
}
}
class PDFGenerator implements ReportGenerator {
public PDFGenerator() {
System.out.println("2...開始初始化 PDFGenerator ...");
}
publicvoid generate(Table table) {
System.out.println("generate an PDF report ...");
}
}
//------------ 以上均無甚重要改變 ----------------- //
class Container {
// 以鍵-值對形式保存各類所需組件 Bean
privatestatic Map<String, Object> beans;
public Container() {
System.out.println("1...開始初始化 Container ...");
beans = new HashMap<String, Object>();
// 建立、保存具體的報表生起器
ReportGenerator reportGenerator = new PDFGenerator();
beans.put("reportGenerator", reportGenerator);
// 獲取、管理 ReportService 的引用
ReportService reportService = new ReportService();
// 注入上面已建立的具體 ReportGenerator 實例
reportService.setReportGenerator(reportGenerator);
beans.put("reportService", reportService);
System.out.println("5...結束初始化 Container ...");
}
publicstatic Object getBean(String id) {
System.out.println("最後獲取服務組件...getBean() --> " + id + " ...");
returnbeans.get(id);
}
}
class ReportService {
// private static ReportGenerator generator = new PDFGenerator();
// 消除上面的緊耦合關係,由容器取而代之
// private ReportGenerator generator = (ReportGenerator) Container
// .getBean("reportGenerator");
// 去除上面的「主動」查找,提供私有字段來保存外部注入的對象
private ReportGenerator generator;
// 以 setter 方式從外部注入
publicvoid setReportGenerator(ReportGenerator generator) {
System.out.println("4...開始注入 ReportGenerator ...");
this.generator = generator;
}
private Table table = new Table();
public ReportService() {
System.out.println("3...開始初始化 ReportService ...");
}
publicvoid getDailyReport(Date date) {
table.setDate(date);
generator.generate(table);
}
publicvoid getMonthlyReport(Month month) {
table.setMonth(month);
generator.generate(table);
}
}
publicclass Client {
publicstaticvoid main(String[] args) {
// 初始化容器
new Container();
ReportService reportService = (ReportService) Container
.getBean("reportService");
reportService.getDailyReport(new Date());
// reportService.getMonthlyReport(new Date());
}
}
運行結果:
1...開始初始化 Container ...
2...開始初始化 PDFGenerator ...
3...開始初始化 ReportService ...
4...開始注入 ReportGenerator ...
5...結束初始化 Container ...
最後獲取服務組件
...getBean() --> reportService ...
generate an PDF report ...
注意:
1、根據上面運行結果的打印順序,可見代碼中加入的具體編號是合理的,模擬了程序執行的流程,因而也就再也不畫序列圖了。
2、注意該例子中對IoC、DI的使用,是以ReportService爲客戶端(即組件需求者)爲基點的,而代碼中的Client 類main()中的測試代碼纔是服務組件的最終用戶,但它須要的不是組件,而是組件所具備的服務。
3、實際在Spring框剪中,初始化Container顯然不是最終用戶Client應該作的事情,它應該由服務提供方事先啓動就緒。
4、在最終用戶Client中,咱們仍是用到Container.getBean("reportService")來獲取事先已在Container的構造函數中實例化好的服務組件。而在具體應用中,一般是用XML等配置文件將可用的服務組件部署到服務器中,再由Container讀取該配置文件結合反射技術得以建立、注入具體的服務組件。
分析:
以前是由ReportService主動從Container中請求獲取服務組件,而如今是被動地等待Container注入(Inject,也就是Push)服務組件。控制權明顯地由底層模塊(ReportService 是組件需求者)轉移給高層模塊(Container 是組件提供者),也就是控制反轉了。
回頭看看上一篇文章吧:-D,應該更能幫助理清例子的演進歷程。
演進式例解控制反轉(IoC)、依賴注入(DI)之一
其餘2種依賴注入方式:
上面用到的是setter方式的依賴注入,還有constructor方式的構造器注入、接口注入。
1、constructor 方式
與setter 方式很相似,只不過有所差別,例如:若是有過多組件須要注入,constructor方式則會形成參數列表過長;也比較僵化,由於該注入只發生在構造期,而setter 方式或者比較靈活些,須要時則注入。
2、接口方式
聽說該方式的注入不經常使用,一些IoC框架如Spring也不怎麼支持,問題在於其真的是比較麻煩:定義特定interface,並聲明所需接口(即待實現的Method),最後組件類經過實現該interface 中的特定Method 進行組件依賴注入。既然少用,也不給出代碼了。
小結:
感受按照着逐步演進的步驟來理解一個問題的出現、分析緣由、解決、分析結果是比較容易接收的,你以爲呢?