依賴反轉原則 | Dependency Inversion Principle

Posted by Ian Tsai on Monday, September 14, 2020

本系列文章同步分享於IT邦幫忙第12屆鐵人賽


Design Pattern 系列文章導讀


Design Pattern可以說是開發上大家都會遇到的一個課題, 這系列文會從Design Principles、各種design pattern到最後的Anti-Patterns & Code Smells介紹下去,讓我們可以更了解各種pattern的使用時機與場合。 預計目標主題如下,若有哪部分不熟的章節可以直接點進去看
註:可以利用Online Java Compiler IDE

  1. 設計模式 - 入門篇
  2. 設計模式 - 原則篇 | Design Principles
  3. 設計模式 - 創建型模型篇 | Creational Patterns
  4. 設計模式 - 結構型模型篇 | Structural Patterns
  5. 設計模式 - 行為型模型篇 | Behavioural Patterns
  6. 設計模式 - 番外篇
  • 定義


依賴反轉原則(DIP)的定義有兩點:

1. High-level modules should not depend on low-level modules. Both should depend on abstractions.

翻譯年糕:高層模組不應該依賴低層模組,兩者皆應依賴於抽象。

2. Abstractions should not depend on details. Details should depend on abstractions.

翻譯年糕:抽象不應該依賴細節,細節應該依賴於抽象。

高層低層是相對關係,其實也就是呼叫者與被呼叫者。而細節指的是具體的實作,相較於抽象的穩定,細節的變化較多。

比如說,到賣場消費要付款時,我使用信用卡付款:

class Payment{
    public void pay(CreditCard creditCard){
        creditCard.pay();
    }
}

class CreditCard{
    public void pay(){
        System.out.println("信用卡付款");
    }
}

public class Bank {
    public static void main(String args[]) {
      Payment p = new Payment();
      p.pay(new CreditCard());
    }
}

output

信用卡付款

依照上面的程式碼,就順利的使用信用卡完成了付款了!信用卡即是低層次模組,而付款這個行為就是高層次模組。可以發現付款這個行為跟信用卡有直接的關係。想想我們從以前用現金、金融卡、信用卡到現在電子支付甚至虛擬貨幣…等等。隨著時代的進步,會有更多的付款方式出現,但如果照著上面的方式去撰寫系統的話,每當出現一個新的付款方式,就要去更動一次程式,這不是個理想的狀態。

  • Dependency Inversion Principle


上述提到,付款依附著信用卡,並不是我們想看到的。於是我們可以將付款行為抽出:

interface IPayment{
    public void pay();
}


class CreditCard implements IPayment{
    public void pay(){
        System.out.println("信用卡付款");
    }
}

class QRCode implements IPayment{
    public void pay(){
        System.out.println("QRCode付款");
    }
}

class Payment{
    public void pay(IPayment iPayment){
        iPayment.pay();
    }
}

public class Bank {
    public static void main(String args[]) {
      Payment p = new Payment();
      p.pay(new CreditCard());
      p.pay(new QRCode());
    }
}

output

信用卡付款
QRCode付款

經過以上的修改,抽出了一個IPaymeny作為interface,付款方式CreditCard和QRCode去實作IPayment,而Payment只要將只要將IPayment引入即可。在實作前他不需要知道付款方式是什麼,在實作時把對應的方式放入即可。如此一來,假設未來要增加虛擬貨幣的付款,只需要新增一個blockchain的class即可,不需要再動到Payment。

剛剛我們一直在解釋定義的第一點,而第二點抽象不應該依賴細節,細節應該依賴於抽象又是什麼意思呢?我們可以把信用卡以及QRCode看成付款的細節(變化度高),而這兩個付款方式應該依照著抽象類別(穩定度高)IPayment去實作,而不是IPayment去照著信用卡或是QRCode的方向走。

  • 小結


DIP的優點
1. 减少class間的耦合性,提高系统的穩定性
2. 降低開發時的風險
3. 提高系統可讀及維護性
DIP的目標

高層模組(Payment)不應該依賴低層模組(CreditCard),兩者皆應依賴於抽象(IPayment)

抽象(IPayment)不應該依賴細節(CreditCard),細節(CreditCard)應該依賴於抽象(IPayment)

  • 範例程式碼


範例1:未使用DIP

範例2:使用DIP

  • References


  • 依賴倒置原則 (Dependency-Inversion Principle, DIP)
  • 依賴反轉原則
  • 依賴反轉原則 Dependency Inversion Principle (DIP)
  • SOLID 原則 - Dependency Inversion Principle(依賴反轉原則)
  • 依赖倒置原则DIP