Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
依賴注入 DI 控制反轉 IOCphp
Inversion Of Controljava
什麼是控制反轉 ?
簡單的說,從主動變被動就是控制反轉
。git
控制反轉是一個很普遍的概念, 依賴注入是控制反轉的一個例子,但控制反轉的例子還不少,甚至與軟件開發無關。程序員
傳統的程序開發,人們老是從 main 函數開始,調用各類各樣的庫來完成一個程序。這樣的開發,開發者控制着整個運行過程
。而如今人們使用框架(Framework)開發,使用框架時,框架控制着整個運行過程
。github
對比如下的兩個簡單程序。面試
一、簡單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框架控制
。ide
雖然兩個程序都有個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類:
實例
綁定(zhang3 Phper)類型
綁定(Phper)接口綁
定(Coder)雖然都是綁定, 從Step1,Step2 到 Step3 靈活性、可擴展性是依次提升的。
Step1 做爲反面教材不可取, 至因而否須要從 Step2 提高爲 Step3, 要看具體狀況。
依賴注入(DI)實現了控制反轉(IoC)的思想,看看怎麼反轉的:
新建
一個 Phper 賦值給 owner(這裏多是新建,也多是在容器中獲取一個現成的 Phper,是新建仍是獲取可有可無,關鍵是主動賦值
)。被動賦值
的,誰來賦值,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