Arthur J. Riel 在《物件導向設計啟示錄》中提出的 61 條啟發式規則,並非要你教條遵守,「違背也不會被處以宗教刑罰」。但請將它們視為設計警鐘:一旦違背,警鐘響起,提醒你審視當下的決策是否合理。以下將這 61 條原則歸類整理,並輔以現代 PHP 開發視角的註解,助你構建高內聚、低耦合、可演進的系統。
所有數據成員必須 private。拒絕 public 屬性,甚至 protected 也要慎用。PHP 8.1+ 的 readonly 屬性配合建構子,是實現此原則的利器。
定義清晰的 public 方法作為契約。類內部實現細節變更不應影響呼叫端。這是「面向介面編程」的基石。
類的公共方法越少越好。每個方法都是潛在的耦合點。接口隔離原則 (ISP) 的直接體現。
所有類應支援:深/淺拷貝(__clone)、相等比較、序列化輸出、從標準格式(JSON/陣列)解構。PHP 的 JsonSerializable、__serialize/__unserialize 機制正是為此而生。
共用邏輯抽取為 private 方法,而非 public。protected 僅供繼承體系內部複用。
不要暴露使用者「用不上」或「不關心」的方法(如內部初始化、緩存清理等)。
類之間要么無關,要么僅通過 public 接口交互。嚴禁直接訪問對方 private/protected 成員(反射除外,且慎用)。
一個類只代表一個核心概念。同一包內的類應對同類變化共同封閉:若變化波及一個類,通常波及包內所有類,但不影響其他包。這是模組化設計的指引。
發現「頻繁透過 getter 從別的對象取數據再操作」?違反了此原則。行為應搬到數據所在的類。Tell, Don't Ask。
互不溝通的行為拆分到不同類。依賴應指向穩定、抽象的方向(介面、抽象類),而非具體實現。
「用戶」是角色,「賬戶」、「個人資料」、「權限策略」才是類。不要把角色當類建模。
頂層類應協作分擔職責,避免「上帝類」包辦一切。
名稱含 Manager、Handler、Service、System、Util、Helper 的類極易腐化為全能類。規劃接口,而非實現接口。
滿屏 getXxx/setXxx 通常意味著「貧血模型」:數據與行為分離。重構機會來了。
類內方法互不呼叫、操作不同屬性組?拆分之。
經典 MVC 依賴規則。PHP 模板引擎(Twig、Blade)只應讀取模型數據,模型零依賴視圖。
但為了遵守上述原則(如避免全能類、功能均勻分布),有時必須偏離純現實映射。設計服務於原則,原則服務於可維護性。
若類無行為、僅攜帶數據,它應是值對象或嵌入屬性,而非獨立實體。
只向系統發消息、不接收消息的類,屬於外部整合層(如 API Client),不應混入核心域模型。
動詞命名、單一方法的類(如 CalculateTotal)通常應遷移至相關領域類為方法。名詞建模,動詞賦能。
分析階段引入的代理,設計階段若無實際委派職責,果斷刪除。
扇出 = 定義的消息數 × 發送的消息數。高扇出意味著類過於複雜、難測試、難復用。拆分職責、引入中介者、使用領域事件解耦。
組合關係意味著外層類會調用內層對象方法。別持有不用的對象。
大多數方法應使用大多數屬性。若某方法只用一兩個屬性,考慮提取為獨立類(內聚性檢驗)。
超過 6 個屬性/協作者,將邏輯相關者分組,封裝為新的值對象或聚合根。
功能在繼承層次中垂直分布。約束優先在類定義(型別系統)實現;若導致類爆炸,則在建構子/行為中驗證。
建構子驗證時,將檢查下沉到最具體的子類層級,避免基類建構子過於膨脹。
常變 → 集中式第三方對象(策略模式);少變 → 分散在相關類中。
類知道自己包含什麼(組合),但不應知道誰包含自己(無反向導航)。
同一個類包含的多個屬性對象之間,不應直接調用。協作由宿主類統籌。
IS-A 關係。代碼復用請用組合/委派,別濫用繼承。
單向依賴。基類零耦合具體子類,支援開放封閉原則 (OCP)。
繼承破壞封裝。子類需要數據?提供 protected 存取器方法,而非暴露欄位。
繼承鏈深度不超過人腦短期記憶極限(6)。過深必重構。
具體類不應被繼承。final 是好朋友。
數據、行為、接口的共同點盡量推到繼承鏈高端。但警惕「拒絕繼承」(子類不需要父類某功能) —— 違反里氏替換原則 (LSP) 的信號。
提取為獨立類,各類包含它。而非繼承。
提取公共基類。經典模板方法模式適用場景。
若只共享方法簽名但無多態調用,用介面 + 組合,別用繼承。
if ($obj instanceof A) ... elseif ($obj instanceof B) ... 是設計失敗的氣味。引入共同介面,實現多態分發。
if ($user->type === 'admin') ... → AdminUser 繼承 User。屬性值變類型。
運行時切換類型(如訂單狀態:新建→付款→發貨)用狀態模式(組合),別用繼承。
若某子類在系統中永遠只有一個實例,它本質是對象,不是類。內聯回父類或用單例模式(慎用)。
動態生成類通常意味著你需要的是「對象實例」的動態屬性/行為,而非新類型。用數據驅動設計。
子類為滿足介面而實現空方法,違反 LSP。介面拆細、或用空對象模式。
「可選擁有某功能」用組合/策略模式。強行繼承會導致類爆炸。
框架定義骨架與擴展點(Template Method、Hook),組件只是葉子節點。框架才能真正復用架構。
PHP 不支援多重繼承(僅 trait),但介面可多實現。若真需多繼承,先證明組合/介面無法解決。
多介面實現時,確保無基類實際是另一基類的子類(避免隱式鑽石問題)。
包含(組合)語義更強、生命週期綁定更緊密、封裝性更好。關聯鬆散,慎用。
用 static 類屬性/方法、或單例服務(如 Cache::get())。全域狀態是測試與並發的大敵。
目錄結構、命名空間、文件分割服務於邏輯架構,而非主導它。但物理約束(如部署單元、團隊邊界)可作為邏輯決策參考輸入。
反射、屬性掛鉤、直接 $obj->prop = ...(若非 private)均屬違規。狀態變更必須經過領域方法,保證不變量。
這 61 條規則不是死板清單,而是設計嗅覺的訓練手冊。將它們內化為直覺,你在寫下每一行 class、每一個 public function 時,腦海中就會自動掃描潛在違規。PHP 現代特性(型別系統、readonly、屬性掛鉤、enum、trait)給了我們更強工具去踐行這些原則。下次重構時,不妨帶著這份清單做一次「設計體檢」—— 你會驚訝於發現的隱患之多,也會慶幸於修復後架構的清爽。