依賴注入 DI 控制反轉 IOC 概念 案例 MD

Markdown版本筆記 個人GitHub首頁 個人博客 個人微信 個人郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

依賴注入 DI 控制反轉 IOCphp


目錄

控制反轉 IOC

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。
不要打電話給咱們,咱們會打給你(若是合適)

這是好萊塢電影公司對面試者常見的答覆。

事實上,不僅電影行業,基本上全部公司人力資源部對面試者都會說相似的話,讓面試者從主動聯繫轉換爲被動等待。

依賴注入 Dependency injection

這裏經過一個簡單的案例來講明。在公司裏有一個常見的案例:把任務指派個程序員完成。

Step1 設計

把這個案例用面向對象的方式來設計,咱們建立兩個類: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包)的使用者一般不須要也不該該來修改類庫的源碼,咱們很天然的想到,應該讓用戶來指派任務負責人,因而有了新的設計。

Step2 設計

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。

Step3 設計

咱們發現無論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交出賦值權從主動賦值到被動賦值,這就是控制反轉。

JAVA中依賴注入的幾種方式

經過set方法注入

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

相關文章
相關標籤/搜索