iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Software Development

文科生的軟體工程啟蒙:用一個代購 App,看懂 30 個系統設計觀念系列 第 16 篇

Day 16:SOLID 原則入門(二)——開放封閉原則(OCP)與依賴反轉原則(DIP)的抽象思維

  • 分享至 

  • xImage
  •  

昨天的 SRP 讓每個類別、模組都只負責一件事。但職責分乾淨之後,還有一個問題沒解決:當系統要「新增」一項功能時,該怎麼加,才不會又把已經寫好、測過的程式碼弄壞?今天要談的開放封閉原則(OCP)與依賴反轉原則(DIP),就是在回答這個問題——而且這兩個原則其實是一體兩面。

一、開放封閉原則(Open/Closed Principle, OCP)

定義:模組應該「對擴充開放(Open for Extension)」,同時「對修改封閉(Closed for Modification)」。換句話說,新增功能時應該透過「加入新的程式碼」來達成,而不是回頭去改動既有、已經穩定且經過測試的程式碼。

常見的實作手法:

  1. 抽象與介面:定義一份抽象介面,具體的行為交給實作介面的新類別去完成,原本的程式碼完全不用動。
  2. 策略模式(Strategy Pattern):把不同的演算法或行為各自包成獨立的類別,只要共用同一份介面,就能互相替換。
  3. 依賴注入(Dependency Injection):在執行時期才決定要用哪個實作,而不是在程式碼裡寫死。

二、依賴反轉原則(Dependency Inversion Principle, DIP)

定義:DIP 包含兩條規則:

  1. 高層模組不應該直接依賴低層模組,兩者都應該依賴於抽象(例如介面)。
  2. 抽象不應該依賴細節,細節(具體實作)應該依賴抽象。

容易混淆的地方:DIP 常常被跟依賴注入(DI)搞混,但兩者不是同一件事。DIP 是一種設計哲學,講的是「依賴關係該往哪個方向流動」;DI 只是實現這個原則的技術手法(透過工廠函式或框架,在執行時期把實作注入進去)。如果注入的東西本身還是一個具體的低層細節,而不是抽象介面,那即使用了 DI,也沒有真正做到依賴反轉——耦合依然存在,只是換了個地方發生。

三、代購 App 案例

延續 Day 15 的 LineNotificationService:目前訂單成立後只會發送 LINE 通知。如果現在要新增 Email 通知,沒有用抽象的做法,就得回頭修改呼叫端的程式碼,加一段 if/else 判斷要用哪種通知方式——這就違反了 OCP。

// notifications/notificationChannel.js —— 抽象介面(JS 沒有原生 interface,用約定的方法簽名表示)
class NotificationChannel {
  notify(buyerId, message) {
    throw new Error('必須實作 notify 方法');
  }
}

// notifications/lineNotificationService.js —— 舊有實作,完全不用修改
class LineNotificationService extends NotificationChannel {
  notify(buyerId, message) { /* 呼叫 LINE API */ }
}

// notifications/emailNotificationService.js —— 新增功能,只是「加」一個新類別
class EmailNotificationService extends NotificationChannel {
  notify(buyerId, message) { /* 呼叫 Email API */ }
}

// orderService.js —— 高層模組只依賴抽象 NotificationChannel,不管背後是哪個實作
function createOrder(order, notifier /* NotificationChannel 的實例 */) {
  const total = order.price * order.qty + order.shippingFee;
  notifier.notify(order.buyerId, `訂單總額 ${total} 元`);
}

新增 EmailNotificationService 的過程中,LineNotificationService 跟 createOrder 一行都沒有被改到——這就是「對擴充開放、對修改封閉」。而 createOrder 從頭到尾只認得 NotificationChannel 這個抽象,不知道也不需要知道呼叫端傳進來的到底是 Line 還是 Email 的實作——這就是依賴反轉。

四、結論

OCP 講的是「新增功能不要動舊程式碼」,而做到這件事的關鍵手法,正是讓高層模組依賴抽象而非具體實作——這正是 DIP 的核心。Day 15 的 SRP 讓每個模組只做一件事,Day 16 的 OCP 與 DIP 則讓這些各司其職的模組之間,透過抽象介面銜接,而不是直接綁死在對方的具體實作上——這樣未來要替換或新增實作時,才不會牽動整個系統。


上一篇
Day 15:SOLID 原則入門(一)——單一職責原則(SRP)在函式與模組拆分上的實踐
下一篇
Day 17:模組化模式——模組匯出(ES Modules)、命名空間與工廠模式(Factory Pattern)基礎
系列文
文科生的軟體工程啟蒙:用一個代購 App,看懂 30 個系統設計觀念 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言