博文位址
我的GitHub | 我的部落格 | 我的微信 | 我的郵箱 |
---|---|---|---|
baiqiantao | bqt20094 | [email protected] |
目錄
- 控制反轉 IOC
- 什麼是控制反轉
- 兩個簡單程式
- 差別
- 依賴注入 Dependency injection
- Step1 設計
- Step2 設計
- Step3 設計
- 總結
- 依賴注入實作了控制反轉的思想
- JAVA中依賴注入的幾種方式
- 通過set方法注入
- 通過構造器注入
- 通過注解注入
- 通過接口注入
Inversion Of Control
簡單的說,
從主動變被動就是控制反轉
。
控制反轉是一個很廣泛的概念, 依賴注入是控制反轉的一個例子,但控制反轉的例子還很多,甚至與軟體開發無關。
傳統的程式開發,人們總是從 main 函數開始,調用各種各樣的庫來完成一個程式。這樣的開發,
開發者控制着整個運作過程
。而現在人們使用架構(Framework)開發,使用架構時,
架構控制着整個運作過程
簡單java程式
public class Activity {
public Activity() {
this.onCreate();//------------開發者主動調用onCreate()方法------------
}
public void onCreate() {
System.out.println("onCreate called");
}
public static void main(String[] args) {
Activity a = new Activity();
}
}
簡單Android程式
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) { //------------架構自主調用onCreate()方法------------
super.onCreate(savedInstanceState);
System.out.println("onCreate called");
}
}
這兩個程式最大的差別就是,
前者程式的運作完全由開發控制,後者程式的運作由Android架構控制
雖然兩個程式都有個onCreate方法,但在前者程式中,如果開發者覺得onCreate名稱不合适,想改為Init,沒問題,直接就可以改; 相比下,後者的onCreate名稱就不能修改,因為後者使用了架構,享受架構帶來福利的同時,就要遵循架構的規則。
這就是控制反轉。
可以說,
控制反轉是所有架構最基本的特征,也是架構和普通類庫最大的不同點
控制反轉還有一個漂亮的比喻,好萊塢原則(Hollywood principle):
don't call us, we'll call you。
不要打電話給我們,我們會打給你(如果合适)
這是好萊塢電影公司對面試者常見的答複。
事實上,不隻電影行業,基本上所有公司人力資源部對面試者都會說類似的話,讓面試者從主動聯系轉換為被動等待。
這裡通過一個簡單的案例來說明。在公司裡有一個常見的案例:把任務指派個程式員完成。
把這個案例用面向對象的方式來設計,我們建立兩個類:Task 和 Phper (php 程式員)
public class Phper {
private String name;
public Phper(String name){
this.name=name;
}
public void writeCode(){
System.out.println(this.name + " is writing php code");
}
}
public class Task {
private String name;
private Phper owner;
public Task(String name){
this.name =name;
this.owner = new Phper("zhang3");//-----------關鍵看這裡-----------
}
public void start(){
System.out.println(this.name+ " started");
this.owner.writeCode();
}
}
測試:
public class MyFramework {
public static void main(String[] args) {
Task t = new Task("Task #1");
t.start();
}
}
運作結果:
Task #1 started
zhang3is writing php code
我們看一看這個設計有什麼問題。
如果同僚仰慕你的設計,要重用你的代碼,你把程式打成一個類庫(jar包)發給同僚。現在問題來了:同僚發現這個Task 類 和 程式員 zhang3 綁定在一起,他所有建立的Task,都是程式員zhang3負責,他要把一些任務指派給Lee4, 就需要修改Task的源程式, 如果沒有Task的源程式,就無法把任務指派給他人。
而類庫(jar包)的使用者通常不需要也不應該來修改類庫的源碼,我們很自然的想到,應該讓使用者來指派任務負責人,于是有了新的設計。
Phper不變。
public class Task {
private String name;
private Phper owner;
public Task(String name){
this.name =name;
}
public void setOwner(Phper owner){//-----------關鍵看這裡,可按需指派-----------
this.owner = owner;
}
public void start(){
System.out.println(this.name+ " started");
this.owner.writeCode();
}
}
public class MyFramework {
public static void main(String[] args) {
Task t = new Task("Task #1");
Phper owner = new Phper("lee4");
t.setOwner(owner);//使用者在使用時按需指派特定的PHP程式員
t.start();
}
}
這樣使用者就可在使用時指派特定的PHP程式員。
我們知道,Task類依賴Phper類,之前,Task類綁定特定的執行個體,現在這種依賴可以在使用時按需綁定,這就是依賴注入(DI)。
這個例子中,我們通過方法
setOwner
注入依賴對象,另一個常見的注入方式是在Task的構造函數中注入:
public Task(String name,Phper owner){
this.name = name;
this.owner = owner;
}
在Java開發中,把一個對象執行個體傳給一個建立對象的情況十分普遍,通常這就是注入依賴,Step2 的設計實作了依賴注入。
我們來看看Step2 的設計有什麼問題。
如果公司是一個單純使用PHP的公司,所有開發任務都有Phper 來完成,這樣這個設就已經很好了,不用優化。但是随着公司的發展,有些任務需要JAVA來完成,公司招了Java程式員,現在問題來了,這個Task類庫的的使用者發現,任務隻能指派給Phper,一個很自然的需求就是,Task應該即可指派給Phper也可指派給Javaer。
我們發現不管Phper 還是 Javaer 都是Coder(程式員), 把Task類對
Phper
類的依賴改為對
Coder
的依賴即可。
新增Coder接口
public interface Coder {
void writeCode();
}
修改Phper類實作Coder接口
public class Phper implements Coder {
private String name;
public Phper(String name){
this.name=name;
}
@Override
public void writeCode(){
System.out.println(this.name + " is writing php code");
}
}
新類Javaer實作Coder接口
public class Javaer implements Coder {
private String name;
public Javaer(String name){
this.name=name;
}
@Override
public void writeCode(){
System.out.println(this.name + " is writing Java code");
}
}
修改Task由對Phper類的依賴改為對Coder的依賴
public class Task {
private String name;
private Coder owner;
public Task(String name) {
this.name = name;
}
public void setOwner(Coder owner) {
this.owner = owner;
}
public void start() {
System.out.println(this.name + " started");
this.owner.writeCode();
}
}
測試
public class MyFramework {
public static void main(String[] args) {
Task t = new Task("Task #1");
Coder owner = new Phper("lee4");
//Coder owner = new Javaer("Wang5");
t.setOwner(owner);
t.start();
}
}
現在使用者可以和友善的把任務指派給 Javaer 了,如果有新的 Pythoner 加入,沒問題,類庫的使用者隻需讓 Pythoner 實作 Coder 接口,就可把任務指派給 Pythoner, 無需修改 Task 源碼, 提高了類庫的可擴充性。
回顧一下,我們開發的Task類:
- 在Step1 中,Task與特定
綁定(zhang3 Phper)執行個體
- 在Step2 中,Task與特定
綁定(Phper)類型
- 在Step3 中,Task與特定
定(Coder)接口綁
雖然都是綁定, 從Step1,Step2 到 Step3 靈活性、可擴充性是依次提高的。
Step1 作為反面教材不可取, 至于是否需要從 Step2 提升為 Step3, 要看具體情況。
依賴注入(DI)實作了控制反轉(IoC)的思想,看看怎麼反轉的:
- Step1 設計中,任務 Task 依賴負責人 owner, 是以就主動
一個 Phper 指派給 owner(這裡可能是建立,也可能是在容器中擷取一個現成的 Phper,是建立還是擷取無關緊要,關鍵是建立
)。主動指派
- 在 Step2 和 Step3 中, Task 的 owner 是
的,誰來指派,Task 自己不關心,可能是類庫的使用者,也可能是架構或容器,Task被動指派
,交出指派權
,這就是控制反轉。從主動指派到被動指派
public class ClassA {
ClassB classB;
public void setClassB(ClassB b) {
classB = b;
}
}
public class ClassA {
ClassB classB;
public void ClassA(ClassB b) {
classB = b;
}
}
public class ClassA {
@inject ClassB classB;//此時并不會完成注入,還需要依賴注入架構的支援,如RoboGuice,Dagger2
//...
public ClassA() {}
}
接口
interface InterfaceB {
void doIt();
}
形式一
class A implements InjectB {
ClassB classB;
@Override
public void injectB(ClassB b) {
classB = b;
}
}
形式二
public class ClassA {
InterfaceB clzB;
public void doSomething() {
clzB = (InterfaceB) Class.forName("...").newInstance();//根據預先在配置檔案中設定的實作類的類名動态加載實作類
clzB.doIt();
}
}
此種接口注入方式因為具備侵入性,它要求元件必須與特定的接口相關聯,是以實際使用有限。
2019-5-12
本文來自部落格園,作者:白乾濤,轉載請注明原文連結:https://www.cnblogs.com/baiqiantao/p/10852293.html