依賴倒置原則


個人博客原文:
依賴倒置原則

雪

設計模式六大原則之三:依賴倒置原則。

簡介

姓名 :依賴倒置原則

英文名 :Dependence Inversion Principle

價值觀 :大男子主義的典型代表,什么都得通過老大或者老爸同意

伴侶 :一定是個溫柔體貼的女子

個人介紹

  1. High level modules should not depend upon low level modules.Both should depend upon abstractions. 高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象(模塊間的依賴通過抽象發生,實現類之間不發生直接的依賴關系,其依賴關系是通過接口或抽象類產生的)
  2. Abstractions should not depend upon details. 抽象不應該依賴細節(接口或抽象類不依賴於實現類)
  3. Details should depend upon abstractions. 細節應該依賴抽象(實現類依賴接口或抽象類)

給大家講個故事,我胡亂想的,如有雷同,肯定是英雄所見略同。那必須交個朋友。

一個小村里,有兩家飯館,雖然掛着不同的牌子,挨在一起,但是老板確是表兄弟。這兩兄弟摳得很,為了節省成本,密謀了一個想法:在兩家飯館誰家忙的時候,可以讓不忙的那家的員工過去支援一下。這樣子,本來每家飯館都需要 2 個洗碗工,總共需要 4 個,他們就只招了 3 個,省了 1 個洗碗工的成本,當然不止洗碗工,還有服務員等等。兩兄弟約定了規則:

  1. A 飯館需要支援的時候,B 飯館老板,讓 B 飯館老板選哪個員工去支援,不能直接讓 A 飯館的員工直接找 B 飯館的員工去幫忙,但可以讓 A 飯館員工找 B飯館老板告知需要支援。
  2. 雖然老板權利大,但是也不能說 A 飯館老板直接叫 B 飯館的員工去幫忙。
  3. 員工沒有真實的老板,今天為 A 飯館工作就是 A 飯館的員工,沒有跟定哪個老板。

大概通過這個小故事,描述了依賴倒置原則的基本內容。

代碼復原

下面通過代碼來模擬這個故事。

錯誤的示范

這個錯誤的示范將就看哈,可能有些問題沒描述清楚。

老板和員工抽象

abstract class Boss {

    abstract void support();

    abstract void askHelp(Boss boss);
}

abstract class Staff {

    private String name;

    abstract void service();

    abstract void askHelp(Boss boss);

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

老板具體類

class BossA extends Boss {

    private StaffA staffA;

    public BossA(StaffA staffA) {
        this.staffA = staffA;
    }

    @Override
    void support() {
        staffA.service();
    }

    @Override
    void askHelp(Boss boss) {
        boss.support();
    }

}

class BossB extends Boss {

    private StaffB staffB;

    public BossB(StaffB staffB) {
        this.staffB = staffB;
    }

    @Override
    void support() {
        staffB.service();
    }

    @Override
    void askHelp(Boss boss) {
        boss.support();
    }
}

員工具體類

class StaffA extends Staff {

    public StaffA(String name) {
        this.setName(name);
    }

    @Override
    void service() {
        System.out.println(this.getName() + "提供服務");
    }

    @Override
    void askHelp(Boss boss) {
        boss.support();
    }
}

class StaffB extends Staff {

    public StaffB(String name) {
        this.setName(name);
    }

    @Override
    void service() {
        System.out.println(this.getName() + "提供服務");
    }

    @Override
    void askHelp(Boss boss) {
        boss.support();
    }
}

測試代碼

/** 初始化老板和員工 */
StaffA staffA = new StaffA("A 員工");
StaffB staffB = new StaffB(" B 員工");
Boss bossA = new BossA(staffA);
Boss bossB = new BossB(staffB);

/** A 老板向 B 老板求支援 */
bossA.askHelp(bossB); // 打印出:B 員工提供服務

/** B 員工向 A 老板求支援 */
staffB.askHelp(bossA); // 打印出:A 員工提供服務

好像看起來實現了要求了,但是其實這段代碼沒有按照上面的 3 點規則編寫,破壞了第 3 點規則,老板們的員工沒有用員工的抽象類,破壞了細節依賴抽象這一點。設想一下,假如現在 A 老板把 A 員工辭退了,重新招了個 C 員工,那么怎么實現呢?是不是需要再新增一個 StaffC 類,然后再修改 BossA 類代碼,把 StaffA 換成 StaffC。這樣超級麻煩,在平時寫項目中要時刻考慮這一點:在具體實現類使用其他類,是不是可以用其抽象類?

代碼:

DIPErrorTest.java

正確的示范

看了上面那個憋屈的代碼,再來看下面簡潔的代碼,才會發現依賴倒置原則是多么強大。

老板和員工抽象類

abstract class Boss2 {

    private Staff2 staff;

    public Boss2(Staff2 staff) {
        this.staff = staff;
    }

    abstract void support();

    abstract void askHelp(Boss2 boss);

    public void setStaff(Staff2 staff) {
        this.staff = staff;
    }

    public Staff2 getStaff() {
        return staff;
    }
}

abstract class Staff2 {

    private String name;

    abstract void service();

    abstract void askHelp(Boss2 boss);

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

老板類

class BossImpl extends Boss2 {

    public BossImpl(Staff2 staff) {
        super(staff);
    }

    @Override
    void support() {
        this.getStaff().service();
    }

    @Override
    void askHelp(Boss2 boss) {
        boss.support();
    }
}

員工類

class StaffImpl extends Staff2{

    public StaffImpl(String name) {
        this.setName(name);
    }

    @Override
    void service() {
        System.out.println(this.getName() + "提供服務");
    }

    @Override
    void askHelp(Boss2 boss) {
        boss.support();
    }
}

測試類

/** 正確示范 */
Staff2 staffA2 = new StaffImpl("A 員工");
Staff2 staffB2 = new StaffImpl("B 員工");
Boss2 bossA2 = new BossImpl(staffA2);
Boss2 bossB2 = new BossImpl(staffB2);

/** A 老板向 B 老板求支援 */
bossA2.askHelp(bossB2); // 打印出:B 員工提供服務

/** B 員工向 A 老板求支援 */
staffB2.askHelp(bossA2); // 打印出:A 員工提供服務

/** A 老板辭退了 A 員工,換成了 C 員工 */
Staff2 staffC2 = new StaffImpl("C 員工");
bossA2.setStaff(staffC2);

/** B 員工向 A 老板求支援 */
staffB2.askHelp(bossA2); // 打印出:C 員工提供服務

這代碼相比上面錯誤的示范,簡潔了很多,實現的功能卻更靈活,這就是依賴倒置原則強大的地方,它可以將類的耦合性降低,提供靈活的處理。

代碼:

DIPRightTest.java

最佳實踐

  1. 變量的表面類型盡量是接口或者是抽象類
  2. 任何類都不應該從具體類派生
  3. 盡量不要覆寫基類的方法
  4. 結合里氏替換原則使用
    (來自《設計模式之禪》)

總結

總的來說,要實現依賴倒置原則,要有『面向接口編程』這個思維,掌握好這個思維后,就可以很好的運用依賴倒置原則。

參考資料:《大話設計模式》、《Java設計模式》、《設計模式之禪》、《研磨設計模式》、《Head First 設計模式》

希望文章對您有所幫助,設計模式系列會持續更新,感興趣的同學可以關注公眾號,第一時間獲取文章推送閱讀,也可以一起交流,交個朋友。

公眾號之設計模式系列文章

公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM