前言
本文整理自 Arthur J. Riel《面向對象設計啟示錄》61 條啟發式規則,提煉核心精華並按主題分類,作為 OOD 代碼審查與架構評估的檢查清單。
一、類設計基礎:封裝與接口 (1-10)
- 數據隱藏:所有數據私有化,行為暴露。
- 依賴倒置:使用者依賴公共接口,類不依賴使用者。
- 最小接口:協議盡量精簡,拒絕「上帝類」臃腫 API。
- 標準協議:實現通用契約(拷貝、相等、序列化、toString、解析)。
- 實現細節不入接口:私有輔助方法不暴露。
- 接口純粹性:不提供使用者無法使用/不關心的操作。
- 零耦合或導出耦合:類之間要么無關,要么僅用公共接口交互。
- 單一職責:類只代表一個關鍵抽象。
- 相關數據行為聚合:數據與操作其數據的行為同屬一類(警惕 Anemic Domain Model)。
- 無關信息分離:互不通信的行為拆分到不同類。
二、抽象建模與系統結構 (11-21)
- 建模類而非角色:抽象穩定概念,非臨時角色。
- 功能水平均勻分佈:頂層類協作完成工作,避免單一控制器。
- 拒絕上帝類:警惕
Manager、Controller、System、Driver 命名的大類。
- 規劃接口而非實現:設計階段聚焦契約。
- 警惕大量存取器:暗示數據行為未聚合。
- 警惕互不通信行為:大量無關方法暗示職責過重。
- 模型不依賴視圖:UI 依賴模型,而非反向 (MVC 核心)。
- 盡可能現實建模:除非違背上述原則。
- 移除無用類:降級為屬性或刪除。
- 移除系統外部類:僅發送消息不接收的類屬外部系統。
- 拒絕動詞類:操作不應建模為類 (單方法類除非為 Strategy/Command)。
三、繼承與泛化體系 (22-57)
繼承使用原則
- 最小化協作者:類依賴的其他類數量最少化。
- 最小化消息傳遞:協作消息數量、類型最少化。
- 最小化扇出:定義消息數 × 發送消息數 最小。
- 包含即使用:包含關系意味著調用被包含對象。
- 方法使用大多數數據:類內方法應操作大多數字段(否則拆分)。
- 字段數 ≤ 7±2:超過短時記憶容量需分組封裝新類。
- 窄而深繼承樹:垂直分佈功能,而非水平攤平。
- 語義約束在類定義中實現:構造函數驗證、不變量保證。
- 約束測試下沉:在繼承層次最深處驗證約束。
- 變化約束集中化,穩定約束分布化:第三方對象 vs 各類內部。
- 知子不知父:類知包含物,不知容器。
- 同層級對象不直接交互:透過父類協調 (Mediator)。
繼承建模鐵律
- 僅用於特化層次:IS-A 關係,非代碼共享手段。
- 子知父,父不知子:單向依賴。
- 基類字段私有化:拒絕 protected 字段,提供 protected 方法供子類擴展。
- 理論深度無限,實踐 ≤ 6 層:超過短時記憶負擔。
- 抽象類必為基類,基類必為抽象類:除非葉子類也需實例化。
- 共性上移:數據/行為/接口盡量上移至共同基類。
- 共享數據→包含;共享數據行為→繼承;共享接口需多態時→繼承:三選一決策樹。
- 拒絕顯式類型分支:用多態替換
if (type==X)。
- 拒絕屬性值分支:屬性值轉為派生類 (State/Strategy 模式)。
- 拒絕繼承建模動態語義:運行時類型切換用 State/Strategy 組合。
- 拒絕對象變類:單實例派生類即為設計異味。
- 運行時創建類→創建對象:元編程慎用。
- 空方法覆寫非法:違背 LSP,用組合替代。
- 可選包含≠繼承:裝飾器/策略解決可選功能。
- 設計可復用框架而非組件:Template Method 定義骨架,子類填充。
- 多重繼承默認錯誤:除非能證明必要 (接口多實現除外)。
- 兩問驗證繼承:(1) 子是父的特殊類型?(2) 父是子的部分?
- 菱形繼承檢查:無基類互為派生。
四、關系選擇與物理設計 (58-61)
- 優先包含而非關聯:生命週期綁定、擁有語義更強。
- 拒絕全局變量/函數:用類變量/類方法 (Singleton/Static Utility)。
- 邏輯設計優於物理設計:物理約束服務於邏輯,非主導。
- 嚴禁繞過公共接口修改狀態:封裝底線,反射/友元類慎用。
實戰應用:代碼審查 Checklist
| 審查點 | 對應法則 | 異味識別 |
| 類公共方法 > 20 | #3, #14 | 上帝類、接口污染 |
| 字段 > 10 且方法僅用部分 | #27, #28 | 需拆分聚合類 |
| 繼承深度 > 6 | #40 | 認知負荷過大,重構扁平化 |
| 基類有 protected 字段 | #38 | 封裝破壞,改 protected 方法 |
| 大量 instanceof / type switch | #47 | 缺失多態,引入 Strategy/Visitor |
| 空方法覆寫 | #52 | LSP 違背,改組合 |
| 全局靜態狀態 | #59 | 線程安全、測試難,改單例/注入 |
這 61 條法則不是教條,而是經驗凝結的「設計嗅覺」。內化為直覺,代碼審查時能瞬間定位結構性風險。