Object Oriented Programming

深入了解依賴反向原則 讓我們可以編寫出更好的程式碼

開發者有責任確保軟件不但可用,而且乾淨、可讀、也易於更改。這就是 SOLID 原則派上用場的時候!在這篇文章中,我們會看看其中一個最重要、最常用的原則:依賴反向原則 (DIP)。
深入了解依賴反向原則 讓我們可以編寫出更好的程式碼
深入了解依賴反向原則 讓我們可以編寫出更好的程式碼
In: Object Oriented Programming

本篇原文(標題:Understanding the Dependency Inversion Principle)刊登於作者 Medium,由 Jason 所著,並授權翻譯及轉載。

我們不需要豐富的知識和技能,都可以執行一個程序。── Robert C. Martin

每一個軟件系統都會提供兩個數值:behaviour 和 structure。

開發者有責任確保軟件不但可用,而且乾淨、可讀、也易於更改

這就是 SOLID 原則派上用場的時候!這些原則可以說是指路明燈,在任何情況下,都可以讓開發者創建更好的設計,並避免程式碼發臭。

  • S:單一職責原則 (Single Responsibility Principle)
  • O:開放封閉原則 (Open-Closed Principle)
  • L:里氏替換原則 (Liskov Substitution Principle)
  • I:介面隔離原則 (Interface Segregation Principle)
  • D:依賴反向原則 (Dependency Inversion Principle)

在這篇文章中,我們會看看其中一個最重要、最常用的原則:依賴反向原則 (DIP)。

依賴是什麼?

在深入了解 DIP 之前,我們需要先知道依賴是什麼。

def funcA():
   funcB()

簡單來說,如果 function A 調用 function Bfunction A 就會依賴 function B

每當 function B 變化的時候,function A 就有機會需要更改和重新編譯。

typical-program-1

一般的程序會像上圖一樣,從 main 函式開始調用一些高層函式,然後是中層和低層函式。

依賴反向原則-1

而依賴反向,簡單來說就是轉依賴的方向

在研究如何實踐這個原則之前,讓我們先看看為什麼要套用這個原則吧!

為什麼要套用 DIP?

舉個例子,有一個 App,它會從 SQL 數據庫存取數據,並將結果輸出到 printer。

handler-input-and-output

處理程序 (handler) 就會是查詢數據、和調用 printer 函式來輸出結果。

這個程序看起來沒有什麼問題,但如果我們想:

  • 從 SQL 數據庫更改為 NoSQL 數據庫?
  • 想把結果以 WhatsApp 訊息輸出,而不是 print 出來?

我們就需要編輯處理程序中調用的函式,並改變部分邏輯,以確保數據可以兼容。

而如果我們有很多調用這類函式的處理程序,我們想要添加東西的話,就會需要修改所有函式。一個簡單的要求,在程式碼庫中卻引起了巨大的變化。

這時,DIP 就大派用場了!

處理程序只需要知道從某處存取數據,及輸出結果到某處,它不需要知道其他細節。

細節並不重要。

高層函式不應依賴低層函式,兩者都應依賴於抽象介面 (abstraction)。

DIP 是什麼?

如前文所述,DIP 的意思就是反轉依賴的方向。要反轉依賴的方向,我們可以在調用者 (caller) 和被調用者 (callee) 之間添加一個穩定的抽象介面 (stable abstract interface)

依賴反向原則-2

處理程序不會直接調用數據庫或 printer,而是會調用一個介面。這個介面是一個 policy,用來定義方法簽章 (method signature),也就是其引數 (argument) 和輸出。

處理程序在不知道細節的情況下調用介面,它只會關心輸入輸出,而低層函式就負責處理細節和實作介面。

依賴反向原則-3

請注意,整個反向不單單是依賴關係的反向,而是介面所有權 (interface ownership) 的反向。現在,定義介面的是高層函式。

而高層函式如何定義介面,就會影響低層函式的實作。由此可見,依賴關係就反轉了!

例子

讓我們來看看一些程式碼片段,以深入了解什麼是 DIP 吧!

class SqlDb:
    def get(self):
        print("Getting data from sql db")
def main():
    sqlDb = SqlDb()
    data = sqlDb.get()

如果沒有 DIP,我們會定義一個 SqlDb 類別,並直接在 main 中調用它。在這種情況下,main 就會依賴 SqlDb,而任何在 SqlDb 中的改動,都可能令 main 需要更改。

from abc import ABC

# Interface
class DataInterface(ABC):
   def get(self) -> list:
      pass
      
# Implementations
class SqlDb(DataInterface):
   def get(self):
      print("Get data from sql db")
        
class NoSqlDb(DataInterface):
   def get(self):
      print("Get data from no sql db")
        
# Factory
def getDataHandler() -> DataInterface:
   handler = SqlDb()
   return handler

def main():
   anyDataHandler = getDataHandler()
   data = anyDataHandler.get()
  

在以上的例子中,我們定義了一個抽象類別 DataInterface 來指定 policy。也就是說,DataInterface 類別必須實作 get 函式。

SqlDb 和 NoSqlDb 都是利用 get 函式來實作 DataInterface

main 函式調用 getDataHandler 來獲取 DataInterface。它不會關心 dataHandler 是什麼;只會關心 dataHandler 必須實作一個 get 函式、並回傳一組數據。

如果想轉換到另一個數據庫 firebase,我們只需要建立一個新的 Firebase 類別並實作 get 函式,然後就可以在 getDataHandler 中換掉原本的數據庫了。如此一來,main 函式就不會被影響到。

這也通常被稱為開放封閉原則

好處

現在,你應該也很清楚 DIP 有什麼好處吧!在文章結束之前,讓我們看看其中兩個好處吧!

細節的改變不會影響業務邏輯 (business logic)

細節是不穩定的,我們隨時都可能會更改數據庫或輸出的實作。

但介面就相對比較穩定。

在實作中的一個改動,不一定會影響到介面都要跟著更改。

如此一來,我們就可以在不影響業務邏輯的情況下,在實作中添加更多功能。

不必急於作出關於細節的決定

在 DIP 原則下,業務邏輯可以對細節一無所知。因此,我們就可以有更多時間,來實作之後的細節。

我們的時間越多,就能夠獲取更多信息,來做出正確的決定。

一位好的架構師 (Architect),應該盡量延遲作出所有決定的時間。── Robert C. Martin

有了介面,我們可以暫時利用 Local Storage,就不需要急於選擇數據庫。之後,我們就可以在不影響業務邏輯的情況下,更改實作的細節。

一位好的架構師就應該設計好 policy,讓自己可以在適當的時候,才作出有關細節的決定。

總結

以上就是依賴反向原則的詳細說明!這可說是 5 個 SOLID 原則中最重要和最基礎的概念。

希望你覺得這篇文章有用,我們在下一篇文章再見!

只有特別用心的人,才可以編寫出無瑕的程式碼。── Robert C. Martin

本篇原文(標題:Understanding the Dependency Inversion Principle)刊登於作者 Medium,由 Jason 所著,並授權翻譯及轉載。


作者簡介:  Jason Ngan,Shopee 後端工程師。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。

作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
策略模式 (Strategy Pattern)簡介 讓程式碼拓展起來更容易
Design Pattern

策略模式 (Strategy Pattern)簡介 讓程式碼拓展起來更容易

本篇原文(標題:Understanding the Strategy Pattern )刊登於作者 Medium,由 Jimmy M Andersson 所著,並授權翻譯及轉載。 我們在編寫類別時,有時會用上大量看上去很相似的方法,但礙於它們在計算方式上存在關鍵的差異,讓我們無法編寫一個通用函數,而刪減其他的函數。今天,
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。