面試官:你是如何理解Java中依賴倒置和依賴注入以及控制反轉的?

什麼是依賴(Dependency)?

依賴是類與類之間的鏈接,依賴關係表示一個類依賴於另外一個類的定義,通俗來說java

就是一種須要,例如一我的(Person)能夠買車(Car)和房子(House),Person類依賴於Car類和House類面試

public static void main(String ... args){
        //TODO:

    Person person = new Person();
    person.buy(new House());
    person.buy(new Car());

}

static class Person{

    //表示依賴House
    public void buy(House house){}
    //表示依賴Car
    public void buy(Car car){}
}

static class House{

}

static class Car{

}
複製代碼

依賴倒置 (Dependency inversion principle)

依賴倒置是面向對象設計領域的一種軟件設計原則編程

軟件設計有 6 大設計原則,合稱 SOLID設計模式

一、單一職責原則(Single Responsibility Principle,簡稱SRP )

  • 核心思想: 應該有且僅有一個緣由引發類的變動
  • 問題描述: 假若有類Class1完成職責T1,T2,當職責T1或T2有變動須要修改時,有可能影響到該類的另一個職責正常工做。
  • 好處: 類的複雜度下降、可讀性提升、可維護性提升、擴展性提升、下降了變動引發的風險。
  • 需注意: 單一職責原則提出了一個編寫程序的標準,用「職責」或「變化緣由」來衡量接口或類設計得是否優良,可是「職責」和「變化緣由」都是不能夠度量的,因項目和環境而異。

二、里氏替換原則(Liskov Substitution Principle,簡稱LSP)

  • 核心思想: 在使用基類的的地方能夠任意使用其子類,能保證子類完美替換基類。
  • 通俗來說: 只要父類能出現的地方子類就能出現。反之,父類則未必能勝任。
  • 好處: 加強程序的健壯性,即便增長了子類,原有的子類還能夠繼續運行。
  • 需注意: 若是子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生「畸變」,則建議斷開父子繼承關係 採用依賴、聚合、組合等關係代替繼承。

三、依賴倒置原則(Dependence Inversion Principle,簡稱DIP)

  • 核心思想:高層模塊不該該依賴底層模塊,兩者都該依賴其抽象;抽象不該該依賴細節;細節應該依賴抽象;
  • 說明:高層模塊就是調用端,低層模塊就是具體實現類。抽象就是指接口或抽象類。細節就是實現類。
  • 通俗來說: 依賴倒置原則的本質就是經過抽象(接口或抽象類)使個各種或模塊的實現彼此獨立,互不影響,實現模塊間的鬆耦合。
  • 問題描述: 類A直接依賴類B,假如要將類A改成依賴類C,則必須經過修改類A的代碼來達成。這種場景下,類A通常是高層模塊,負責複雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操做;假如修改類A,會給程序帶來沒必要要的風險。
  • 解決方案: 將類A修改成依賴接口interface,類B和類C各自實現接口interface,類A經過接口interface間接與類B或者類C發生聯繫,則會大大下降修改類A的概率。
  • 好處:依賴倒置的好處在小型項目中很難體現出來。但在大中型項目中能夠減小需求變化引發的工做量。使並行開發更友好。

四、接口隔離原則(Interface Segregation Principle,簡稱ISP)

  • 核心思想:類間的依賴關係應該創建在最小的接口上
  • 通俗來說: 創建單一接口,不要創建龐大臃腫的接口,儘可能細化接口,接口中的方法儘可能少。也就是說,咱們要爲各個類創建專用的接口,而不要試圖去創建一個很龐大的接口供全部依賴它的類去調用。
  • 問題描述: 類A經過接口interface依賴類B,類C經過接口interface依賴類D,若是接口interface對於類A和類B來講不是最小接口,則類B和類D必須去實現他們不須要的方法。
  • 需注意:
  • 接口儘可能小,可是要有限度。對接口進行細化能夠提升程序設計靈活性,可是若是太小,則會形成接口數量過多,使設計複雜化。因此必定要適度
  • 提升內聚,減小對外交互。使接口用最少的方法去完成最多的事情
  • 爲依賴接口的類定製服務。只暴露給調用的類它須要的方法,它不須要的方法則隱藏起來。只有專一地爲一個模塊提供定製服務,才能創建最小的依賴關係。

五、迪米特法則(Law of Demeter,簡稱LoD)

  • 核心思想: 類間解耦。
  • 通俗來說: 一個類對本身依賴的類知道的越少越好。自從咱們接觸編程開始,就知道了軟件編程的總的原則:低耦合,高內聚。不管是面向過程編程仍是面向對象編程,只有使各個模塊之間的耦合儘可能的低,才能提升代碼的複用率。低耦合的優勢不言而喻,可是怎麼樣編程才能作到低耦合呢?那正是迪米特法則要去完成的。

六、開放封閉原則(Open Close Principle,簡稱OCP)

  • 核心思想: 儘可能經過擴展軟件實體來解決需求變化,而不是經過修改已有的代碼來完成變化
  • 通俗來說: 一個軟件產品在生命週期內,都會發生變化,既然變化是一個既定的事實,咱們就應該在設計的時候儘可能適應這些變化,以提升項目的穩定性和靈活性。

依賴倒置原則的定義以下:微信

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

什麼是上層模塊和底層模塊?

無論你認可不認可,「有人的地方就有江湖」,咱們都說人人平等,可是對於任何一個組織機構而言,它必定有架構的設計有職能的劃分。按照職能的重要性,天然而然就有了上下之分。而且,隨着模塊的粒度劃分不一樣這種上層與底層模塊會進行變更,也許某一模塊相對於另一模塊它是底層,可是相對於其餘模塊它又多是上層架構

組織架構
ide


公司管理層就是上層,CEO 是整個事業羣的上層,那麼 CEO 職能之下就是底層。

而後,咱們再以事業羣爲整個體系劃分模塊,各個部門經理以上部分是上層,那麼之下的組織均可以稱爲底層。函數

由此,咱們能夠看到,在一個特定體系中,上層模塊與底層模塊能夠按照決策能力高低爲準繩進行劃分。工具

那麼,映射到咱們軟件實際開發中,通常咱們也會將軟件進行模塊劃分,好比業務層、邏輯層和數據層。this

業務模塊


業務層中是軟件真正要進行的操做,也就是 作什麼

邏輯層是軟件現階段爲了業務層的需求提供的實現細節,也就是怎麼作

數據層指業務層和邏輯層所須要的數據模型。

所以,如前面所總結,按照決策能力的高低進行模塊劃分。業務層天然就處於上層模塊,邏輯層和數據層天然就歸類爲底層。

什麼是抽象和細節?

象如其名字同樣,是一件很抽象的事物。抽象每每是相對於具體而言的,具體也能夠被稱爲細節,固然也被稱爲具象。

好比:

  1. 這是一幅畫。畫是抽象,而油畫、素描、國畫而言就是具體。
  2. 這是一件藝術品,藝術品是抽象,而畫、照片、瓷器等等就是具體了。
  3. 交通工具是抽象,而公交車、單車、火車等就是具體了。
  4. 表演是抽象,而唱歌、跳舞、小品等就是具體。

上面能夠知道,抽象能夠是物也能夠是行爲。

具體映射到軟件開發中,抽象能夠是接口或者抽象類形式。

/** * Driveable 是接口,因此它是抽象 */
public interface Driveable {
    void drive();
}
/** * 而 Bike 實現了接口,它們被稱爲具體。 */
public class Bike implements Driveable {
    @Override
    public void drive() {
        System.out.println("Bike drive");
    }
}
/** * 而 Car實現了接口,它們被稱爲具體。 */
public class Car implements Driveable {
    @Override
    public void drive() {
        System.out.println("Car drive.");
    }
}
複製代碼

依賴倒置的好處

在日常的開發中,咱們大概都會這樣編碼。

public class Person {

    private Bike mBike;
    private Car mCar;
    private Train mTrain;

    public Person(){
        mBike = new Bike();
        //mCar = new Car();
// mTrain = new Train();
    }

    public void goOut(){
        System.out.println("出門啦");
        mBike.drive();
        //mCar.drive();
// mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}
複製代碼

咱們建立了一個 Person 類,它擁有一臺自行車,出門的時候就騎自行車。

不過,自行車適應很短的距離。若是,我要出門逛街呢?自行車就不大合適了。因而就要改爲汽車。

不過,若是我要到北京去,那麼汽車也不合適了。

有沒有一種方法能讓 Person 的變更少一點呢?由於這是最基礎的演示代碼,若是工程大了,代碼複雜了,Person 面對需求變更時改動的地方會更多。

而依賴倒置原則正好適用於解決這類狀況。

下面,咱們嘗試運用依賴倒置原則對代碼進行改造。

咱們再次回顧下它的定義。

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

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

首先是上層模塊和底層模塊的拆分。

按照決策能力高低或者重要性劃分,Person 屬於上層模塊,Bike、Car 和 Train 屬於底層模塊。

上層模塊不該該依賴於底層模塊。

person架構


public class Person {

// private Bike mBike;
    private Car mCar;
    private Train mTrain;
    private Driveable mDriveable;

    public Person(){
// mBike = new Bike();
        //mCar = new Car();
       mDriveable = new Train();
    }

    public void goOut(){
        System.out.println("出門啦");
        mDriveable.drive();
        //mCar.drive();
// mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}
複製代碼

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

控制反轉 (IoC)

控制反轉 IoC 是 Inversion of Control的縮寫,意思就是對於控制權的反轉,對麼控制權是什麼控制權呢?

Person本身掌控着內部 mDriveable 的實例化。

如今,咱們能夠更改一種方式。將 mDriveable 的實例化移到 Person 外面。

public class Person2 {

    private Driveable mDriveable;

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

    public void goOut(){
        System.out.println("出門啦");
        mDriveable.drive();
        //mCar.drive();
// mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}
複製代碼

就這樣不管出行方式怎麼變化,Person 這個類都不須要更改代碼了。

在上面代碼中,Person 把內部依賴的建立權力移交給了 Person2這個類中的 main() 方法。也就是說 Person 只關心依賴提供的功能,但並不關心依賴的建立。

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

好比上面代碼,Person 再也不親自建立 Driveable 對象,它將依賴的實例化的權力交接給了 Person2。而 Person2在 IoC 中又指代了 IoC 容器 這個概念。

依賴注入(Dependency injection)

依賴注入,也常常被簡稱爲 DI,其實在上一節中,咱們已經見到了它的身影。它是一種實現 IoC 的手段。什麼意思呢?

爲了避免由於依賴實現的變更而去修改 Person,也就是說以可能在 Driveable 實現類的改變下不改動 Person 這個類的代碼,儘量減小二者之間的耦合。咱們須要採用上一節介紹的 IoC 模式來進行改寫代碼。

這個須要咱們移交出對於依賴實例化的控制權,那麼依賴怎麼辦?Person 沒法實例化依賴了,它就須要在外部(IoC 容器)賦值給它,這個賦值的動做有個專門的術語叫作注入(injection),須要注意的是在 IoC 概念中,這個注入依賴的地方被稱爲 IoC 容器,但在依賴注入概念中,通常被稱爲注射器 (injector)。

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

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

  1. 構造函數中注入
  2. setter 方式注入
  3. 接口注入
/** * 接口方式注入 * 接口的存在,代表了一種依賴配置的能力。 */
public interface DepedencySetter {
    void set(Driveable driveable);
}
public class Person2 implements DepedencySetter {

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

    private Driveable mDriveable;

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

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

    public void goOut(){
        System.out.println("出門啦");
        mDriveable.drive();
        //mCar.drive();
// mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}
複製代碼

最後

讀到這的朋友以爲不錯能夠點贊關注下,感謝您的支持,之後會不停更新更多精選乾貨及資訊分享,歡迎你們在評論區留言討論!

歡迎關注享學課堂online微信公衆號,天天會持續更新技術乾貨,熱點,吐槽等文章,還有免費的Java架構視頻資料和麪試專題資料免費領取分享,後臺回覆關鍵字【Java資料】,免費獲取Java架構面試專題文檔資料、電子書及更多架構進階視頻資料(視頻+筆記)

相關文章
相關標籤/搜索