softwareArchitecture

深入淺出依賴反向原則 Dependency Inversion Principle

這篇文章介紹軟體架構裡面 S.O.L.I.D 中的D (Dependency Inversion Principle)

這篇文章中大部分的程式碼 參考自SOLID Principles of Object-Oriented Design and Architecture

定義

這個原則有兩點

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.

這篇其實就是Open-Closed Principle的結論:

恰當的抽象 可以讓你的架構多出許多的彈性

換句話說而已

講完了掰掰

 

 

 

 

 

 

 

 

 

 

 

哎 做學問要有始有終 總不能S.O.L.I都認真講了 結果D不講吧

Alt text

好吧 既然要講 就順便講清楚幾個常被搞混的詞

DIP (Dependency Inversion Principle)

DI (Dependency Injection)

IOC (Inversion of Control)

依賴

什麼是依賴呢 依賴就是需要 就像工程師需要依賴電腦來工作一樣

大家都是專業人士 千言萬語比不過幾行程式

class Programmer {
  Computer computer;
  Programmer() {
    computer = new Computer();
  }

  void code(){
    computer.program();
  }
}

沒有電腦的話Programmer什麼都不是 所以Progrommer依賴電腦 就是這麼簡單

Alt text

那既然依賴了電腦 你就必須知道電腦有什麼函式可以呼叫 比如說program() video() game() 或是他刪除了哪些函式的話你的Programmer要跟著改

在這個例子中 程序員就是上層模組 電腦就是下層模組

Dependency Injection 依賴注入

剛剛的例子 我們把物件的創建 寫死在Programmer 要是明天我希望剛上工的程序員拿不同種類的電腦 我要再寫一個新的Programmer 這樣code沒有reuse

依賴注入指的是 與其我們自己創建電腦 不如讓使用我的人創建 讓使用我的人把物件給我

class Programmer {
  Computer computer;
  Programmer(Computer c) {
    computer = c;
  }

  void code(){
    computer.program();
  }
}

更多關於依賴注入的說明 請參考Effective Java Item5 - 依賴注入優於硬連接資源

有趣的是 依賴注入不只這種方式 剛剛介紹的是藉由constructor注入 你也可以用setter注入

class Programmer {
  Computer computer;
  Programmer() {}

  void code(){
    computer.program();
  }
  void setComputer(Computer c) {
    computer = c;
  }
}

控制反轉

控制反轉就是在軟體工程中decouple的思維 把物件的創建這個責任丟給IOC容器 在運行的時候再讓容器將具體的物件動態的注入到需要使用的類別裡

class Work {
  public static void main(String[] args) {
    Computer computer = new Computer();
    Programmer programmer = new Programmer(computer);
  }
}

在我們的例子裡 IOC容器就是Work 但在許多大型一點的Java專案裡 Spring是個很常見的IOC容器 它讓物件的控制流跟物件的創建完全分開 讓程序員更好管理

依賴反向原則

那現在好了 因應新冠肺炎 所有人被強制WFH 不能再用公司裡的桌電上班 只能回家用筆電

class RemoteProgrammer {
  Laptop laptop;
  RemoteProgrammer(Laptop l) {
    laptop = l;
  }
  void code(){
    laptop.program();
  }
}

Alt text

那如果我今天剩下手機 我也是可以上班收發email的吧 那每次拿不同的工具就創建一個新的類別未免也太辛苦了吧

來 看回我們的第一點

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

今天我們的問題是因為高層模組依賴了低層模組 這時候來個可愛的抽象吧

interface Programmable {
    void program();
}

真是可愛 這樣我只要電腦實作這個介面

class Computer implements Programmable {
    @Override
    void program(){
      //computer program
    }
}

我就可以注入到Programmer裡

class Programmer {
  Programmable programmable;
  Programmer(Programmable p) {
    programmable = p;
  }

  void code(){
    programmable.program();
  }
}

太精美了 現在看一下依賴圖

Alt text

原本程序員指向電腦的箭頭不見了 變成他們兩個共同指向Programmable

這就是依賴反向的反向 成為了面向接口的實現

這就是DIP的第二點

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

即使我要讓我的程序員拿筆電或是手機 只要簡單的實作Programmable 擴展性極強!

Alt text

再用依賴注入丟給Programmer 搞定

總結

DIP (Dependency Inversion Principle): S.O.L.I.D 中的D 是個被推崇的設計原則 主要是說上層模組不依賴於下層模組 兩者都應該依賴於抽象

IOC (Inversion of Control): 引入了IOC容器來分離物件的控制流和物件的創造 遵循了DIP

DI (Dependency Injection): 實現IOC的手段 得以由外部注入對物件創建的依賴