面向物件設計(OOD)不僅是語法糖,更是管理複雜性的思維框架。以下 61 條經驗法則源自經典軟體工程實踐,經過 PHP 語境適配整理,旨在幫助開發者構建高內聚、低耦合、可維護、可擴展的系統。
| 類別 | 條目範圍 | 核心思想 |
|---|---|---|
| 類與介面設計 | 1-15 | 封裝、介面最小化、職責單一、避免上帝類 |
| 耦合與內聚 | 7, 9, 10, 14, 15, 22-25 | 零耦合或導出耦合、相關數據行為集中、減少協作者 |
| 繼承與多態 | 29, 36-51 | 僅用於特化層次、里氏替換、避免深層繼承、組合優於繼承 |
| 約束與契約 | 30-33 | 語義約束內建、集中式 vs 分散式權衡 |
| 包含與關聯 | 26, 28, 34, 35, 58 | 包含即使用、對象數量限制、共享作用域無使用關係 |
| 全域狀態與物理設計 | 59, 60 | 類變量替代全域、邏輯設計優先於物理設計 |
| 狀態修改規範 | 2, 61 | 依賴公開介面、不繞過介面修改狀態 |
所有數據隱藏於類內部,僅透過公開介面存取。私有輔助方法不屬於公開介面,不應暴露。
減少公開訊息數量。大量 get/set 方法暗示數據與行為未集中,應重構為行為豐富的領域模型,而非貧血模型。
類應僅代表一個關鍵抽象。警惕命名含Manager、System、Driver、Subsystem的類,往往是職責過重的信號。
朝向穩定方向依賴。依賴抽象(介面)而非具體實現,高層模組不應依賴低層模組,二者皆應依賴抽象。
- 繼承僅用於建模「is-a」特化層次(條目 36, 56)
- 基類應為抽象類,派生類必須知道基類,基類不知派生類(條目 37, 41, 42)
- 基類數據應為私有,避免 protected 數據洩露實作細節(條目 38)
- 繼承深度理論上越深越好,實踐中不超過 6 層(條目 39, 40)
- 共同數據/行為上移至基類(條目 43, 45),共同介面僅在需要多態時提取(條目 46)
- 禁止用繼承建模動態語義(條目 49),禁止將對象變為派生類(條目 50)
可選包含關係勿建模為繼承。面臨包含與關聯選擇時,優先選擇包含(條目 58)。
顯式類型判斷(if ($obj instanceof A))通常違反開閉原則,應用多態機制。屬性值驅動的分支應重構為繼承體系,每個值對應一個派生類。
- 協作者數量最小化(條目 22)
- 類與協作者間訊息傳遞最小化(條目 23, 24)
- 扇出(定義訊息數 × 發送訊息數)最小化(條目 25)
- 方法應使用大多數數據成員(條目 27)
- 類包含對象數不超過短期記憶容量(6±2,條目 28)
- 靜態不變約束 → 構造函數驗證(條目 31)
- 頻繁變動的語義信息 → 集中式第三方對象(條目 32)
- 穩定的語義信息 → 分散在相關類中(條目 33)
嚴禁繞過公開介面直接修改對象狀態。所有狀態變更必須透過方法調用,保證不變約束檢查、事件觸發、日誌記錄等橫切關注點生效。
// 定義穩定契約
interface UserRepository {
public function findById(string $id): ?User;
public function save(User $user): void;
}
// 具體實現依賴抽象
class MySQLUserRepository implements UserRepository {
// ...
}
class UserService {
public function __construct(private UserRepository $repo) {} // 依賴注入
}
// ❌ 貧血模型:只有 get/set,業務邏輯分散在 Service 中
class Order {
private $status;
public function getStatus() { return $this->status; }
public function setStatus($s) { $this->status = $s; }
}
// ✅ 充血模型:封裝業務規則
class Order {
private $status = 'pending';
public function pay(): void {
if ($this->status !== 'pending') throw new \LogicException('訂單狀態不允許支付');
$this->status = 'paid';
$this->recordEvent(new OrderPaid($this->id));
}
}
// ❌ 繼承爆炸:LoggerInterface, FileLogger, DBLogger, EmailLogger...
// ✅ 組合策略
interface LogHandler { public function handle(string $msg): void; }
class Logger {
public function __construct(private iterable $handlers) {}
public function log(string $msg): void {
foreach ($this->handlers as $h) $h->handle($msg);
}
}
這 61 條經驗不是教條,而是導航燈。實際專案中需根據團隊規模、業務複雜度、交付壓力權衡取捨。核心心法只有八字:高內聚、低耦合、單一職責、依賴抽象。持續重構、代碼審查、架構評審,才能讓設計原則真正落地生根。