SRP 是 SOLID 原則中最易被誤解的。多數人以為它是「每個模組應只做一件事(Do one thing)」。
但這是重構(Refactoring)層次,而非架構層次的 SRP。

Uncle Bob 給出 SRP 在架構上的真正定義:

「一個模組應只有一個理由可以被改變。」
(A module should have one, and only one, reason to change.)

什麼叫做「改變的理由」?精確說法是:「模組應只對唯一角色(Actor)負責。」

一、違反 SRP 的症狀#

當我們將具備不同角色(Actor)的程式碼混在一起時,就會發生悲劇。

書中舉了一個經典 Employee 類別為例:
這個 Employee 類別違反了 SRP,因它同時包含了三個不同角色的邏輯:

方法負責角色職責
calculatePay()財務部門(CFO)計算薪水
reportHours()人資部門(COO)報告工時
save()技術部門(CTO)資料庫儲存

Figure 7.1: The Employee class

這會導致兩個嚴重症狀:

意外重複 (Accidental Duplication)#

這最致命。假設 calculatePayreportHours 都依賴一個共用私有演算法 regularHours() 來計算正常工時。

Figure 7.2: Shared algorithm

  • 情境: CFO 決定修改工時的計算方式
  • 災難: 開發者修改 regularHours() 滿足了 CFO,
    但卻意外影響了 COO 的 reportHours() 報表數據(而 COO 對此一無所知)
  • 後果: 系統帶著 Bug 上線,導致數百萬美元的損失

合併衝突 (Merge)#

  • 情境: DBA 正在修改 save()(為了適應資料庫變更),
    同時財務人員正修改 calculatePay()(為了新的薪資政策)
  • 災難: 兩個不同開發者為了不同理由,修改同個原始檔(Source file)
  • 後果: 版本控制的合併衝突,且合併過程中極易出錯

二、解決方案:分離與 Facade#

解決 SRP 違反的策略:分離(Separate)
分開服務不同角色的程式碼,不把它們放在同個類別中。

1. 分離資料與行為#

最簡單的做法是將資料結構(Data Structure)與函數(Functions)分開。

  • 建立一個單純的 EmployeeData 資料結構(無邏輯)
  • 建立三個獨立類別:PayCalculatorHourReporterEmployeeSaver
  • 這些類別只包含各自所需原始碼,互不認識,避免意外重複

Figure 7.3: The three classes do not know about each other

2. Facade 模式 (外觀模式)#

雖然分開了三個類別解決了問題,但對呼叫端(Client)來說變麻煩了(需實例化三個類別)。
這時可用 Facade Pattern

  • 做法: 建立一個 EmployeeFacade 類別
  • 功能: 幾乎沒有邏輯,只負責實例化小類別,並 委託(Delegate) 工作給它們

Figure 7.4: The Facade pattern

Figure 7.5: Employee class as Facade

三、結論#

SRP 告訴我們,架構設計不僅僅是功能的堆砌,更是職責的劃分

  • 識別角色: 寫程式碼前,先問自己:「這段程式碼是為誰而寫?」
  • 隔離變更: 確保某個角色改變需求時,不會影響到其他無關角色