Java內功修煉系列:依賴倒置、控制反轉、依賴注入

目錄

一 名詞解釋

1.1 依賴倒置原則(Dependency inversion principle)

1.2 上層/底層模塊

1.3 依賴(Dependency)

二 依賴倒置

2.1 依賴倒置前

2.1 依賴倒置後

三 控制反轉 (IoC)

四 依賴注入(Dependency injection)


一 名詞解釋

1.1 依賴倒置原則(Dependency inversion principle)

依賴倒置原則來源於軟件設計 6 大設計原則,它的定義如下:

  • 上層模塊不應該依賴底層模塊,它們都應該依賴於抽象。

  • 抽象不應該依賴於細節,細節應該依賴於抽象。

說明:高層模塊就是調用端,低層模塊就是具體實現類。抽象就是指接口或抽象類。細節就是實現類。

通俗來講: 依賴倒置原則的本質就是通過抽象(接口或抽象類)使個各類或模塊的實現彼此獨立,互不影響,實現模塊間的鬆耦合。

問題描述: 類A直接依賴類B,假如要將類A改爲依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責複雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。

解決方案: 將類A修改爲依賴接口interface,類B和類C各自實現接口interface,類A通過接口interface間接與類B或者類C發生聯繫,則會大大降低修改類A的機率。

1.2 上層/底層模塊

類比公司,管理層就是上層,CEO 是整個事業羣的上層,那麼 CEO 職能之下就是底層。各部門經理以上部分是上層,那麼之下的組織都可以稱爲底層。由此,我們可以看到,在一個特定體系中,上層模塊與底層模塊可以按照決策能力高低爲準繩進行劃分。那麼,映射到我們軟件實際開發中,一般我們也會將軟件進行模塊劃分,比如業務層、邏輯層和數據層。

業務層中是軟件真正要進行的操作,也就是做什麼。 邏輯層是軟件現階段爲了業務層的需求提供的實現細節,也就是怎麼做。 數據層指業務層和邏輯層所需要的數據模型。如前面所總結,按照決策能力的高低進行模塊劃分。業務層自然就處於上層模塊,邏輯層和數據層自然就歸類爲底層。

1.3 依賴(Dependency)

簡單一點說,A類中持有B類的引用,A類即依賴於B類。下面例子中,Person持有Car的引用,Person依賴於Car。

public class Person {

    private Car mCar;

    public Person() {
        mCar = new Car();
    }
}

二 依賴倒置

2.1 依賴倒置前

在之前的開發中,我們會這樣寫代碼:

public class Person {

    private Bike mBike;
    private Car mCar;
    private Plane mPlane;

    public Person(){
        mBike = new Bike();
    }

    public void goToBeijing(){
        System.out.println("以某種交通方式去北京");
        mBike.drive();
    }

    public static void main(String ... args){
        Person person = new Person();
        person.goToBeijing();
    }
}

我們創建了一個 Person 類,Person可以選擇任意一種交通方式去北京,當然不管哪種交通方式,開汽車也好坐飛機也好,每種交通方式都包含一個 goToBeijing()的方法供Person調用。

這是基礎代碼,類比實際代碼中,肯定會經常性地更改交通工具,這時會頻繁地更改Person中的依賴對象,也就是需要再Person類中new不同的交通工具(car or plane or bike),代碼更改,多、煩、耦合高。

有沒有方法能解決上述問題呢?依賴倒置!

2.1 依賴倒置後

在利用依賴倒置前,我們需要明白依賴倒置的定義:

  • 上層模塊不應該依賴底層模塊,它們都應該依賴於抽象。
  • 抽象不應該依賴於細節,細節應該依賴於抽象。

首先是上層模塊和底層模塊的拆分,Person依賴交通出具出行,Person 屬於上層模塊,Bike、Car 和 Train 屬於底層模塊。之前的代碼中,Person依賴於交通工具才能出行。

我們分析一下:Person只想去北京,去北京是抽象。具體怎麼去北京,坐飛機還是開汽車,這是細節。上層模塊也就是Person他應該依賴於抽象而不是細節,細節只要實現好去北京具體乘坐哪個交通方式(也就是方法的實現)就好了。

public class Person {

    private Bike mBike;
    private Car mCar;
    private Plane mplane;
    private Driveable mDriveable;

    public Person(){
       mDriveable = new Plane();
    }

    public void goToBeijing(){
        System.out.println("以某種交通方式去北京");
        mDriveable.drive();
    }

    public static void main(String ... args){
        Person person = new Person();
        person.goToBeijing();
    }
}

Person無論選擇哪種出行方式,去北京只要調用goToBeijing方法即可。該方法會自動調用出行交通工具的drive方法,因爲bike也好坐小汽車也好,坐飛機也好,這三種出行方式都是實現了Driveable接口。該接口就是一個抽象,它不在乎你去北京的具體實現,但無論你選擇哪種交通出行方式,都必須drive到北京。

可以看到,依賴倒置實質上是面向接口編程的體現。


三 控制反轉 (IoC)

上述例子中,我們仍然需要對Person類內部進行代碼修改,還是沒有達到目的。因爲Person仍然掌控着自己內部的mDriveable 具體實現類的實例化。 我們需要將 mDriveable 的實例化移到 Person 外面,也就是說,Person將自己掌控着的內部 mDriveable 的實例化的這個控制權反轉交出去了,這就是控制反轉,也就是 IoC (Inversion of Control) 。

public class Person {

    private Driveable mDriveable;

    public Person(Driveable driveable){
        this.mDriveable = driveable;
    }

    public void goToBeijing(){
        System.out.println("以某種交通方式去北京");
        mDriveable.drive();
    }

    public static void main(String ... args){
        Person2 person = new Person2(new Car());
        person.goToBeijing();
    }

}

就這樣無論Person選擇什麼交通方式去北京,都不需要再去更改Person 類的內部了。Person 把內部依賴的創建權力移交給了類中的 main() 方法,它只關心依賴提供的功能,而不關心依賴的創建,不再親自創建 Driveable 對象。而Person這個類在 IoC 中,指代了 IoC 容器這個概念。

這種思想其實就是 IoC,IoC 是一種新的設計模式,它對上層模塊與底層模塊進行了更進一步的解耦。控制反轉的意思是反轉了上層模塊對於底層模塊的依賴控制。


四 依賴注入(Dependency injection)

上面是控制反轉,那麼如何實現控制反轉呢?

答:依賴注入 ,DI , Dependency Injection。也經常被簡稱爲 DI,它是一種實現 IoC 的手段。

上面的例子中我們移交出了Person對於依賴實例化的控制權,那麼依賴怎麼辦?Person 無法實例化依賴了,它就需要在外部(IoC 容器)賦值給它,這個賦值的動作有個專門的術語叫做注入(injection)。

需要注意的是在 IoC 概念中,這個注入依賴的地方被稱爲 IoC 容器,但在依賴注入概念中,一般被稱爲注射器 (injector)。

表達通俗一點就是:我不想自己實例化依賴,你(injector)創建它們,然後在合適的時候注入給我

實現依賴注入有 3 種方式:

  1. 構造函數中注入

  2. setter 方式注入

  3. 接口注入

/**
 * 接口方式注入
 * 接口的存在,表明了一種依賴配置的能力。
 */
public interface DepedencySetter {
    void set(Driveable driveable);
}
public class Person  implements DepedencySetter {

    //接口方式注入
    @Override
    public void set(Driveable driveable) {
        this.mDriveable = mDriveable;
    }

    private Driveable mDriveable;

    //構造函數注入
    public Person(Driveable driveable){
        this.mDriveable = driveable;
    }

    //setter 方式注入
    public void setDriveable(Driveable mDriveable) {
        this.mDriveable = mDriveable;
    }

    public void goToBeijing(){
        System.out.println("以某種交通方式去北京");
        mDriveable.drive();
    }

    public static void main(String ... args){
        Person2 person = new Person2(new Car());
        person.goToBeijing();
    }
}

 

 

 

 

參考文章:

輕鬆學,淺析依賴倒置(DIP)、控制反轉(IOC)和依賴注入(DI):https://blog.csdn.net/briblue/article/details/75093382

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章