PHP 類別只能 extends 一個父類別,但可 implements 多個 介面、use 多個 Trait、或透過組合/委派達到多重繼承效果。三種模式各有適用場景。
定義「能做什麼」而非「怎麼做」,強制實作類別遵守契約。
interface Loggable {
public function log(string $level, string $message): void;
}
interface Cacheable {
public function getCacheKey(): string;
public function getCacheTtl(): int;
}
// 抽象基類提供共用邏輯
abstract class BaseModel {
protected array $attributes = [];
public function __set(string $name, $value) { $this->attributes[$name] = $value; }
public function __get(string $name) { return $this->attributes[$name] ?? null; }
}
// 實作類別同時滿足多個契約
class User extends BaseModel implements Loggable, Cacheable {
public function log(string $level, string $message): void {
error_log("[$level] User#{$this->id}: $message");
}
public function getCacheKey(): string { return "user:{$this->id}"; }
public function getCacheTtl(): int { return 3600; }
}
優點: 解耦、可測試、支援多型。缺點: 無法共用具體實作碼(除非用 Trait)。
Trait 是「編譯時複製貼上」的程式碼片段,可在多個類別間共用方法、屬性、甚至抽象方法。
trait Timestampable {
public function setCreatedAt(\DateTimeInterface $dt): void { $this->created_at = $dt; }
public function setUpdatedAt(\DateTimeInterface $dt): void { $this->updated_at = $dt; }
}
trait SoftDeletable {
public function delete(): void { $this->deleted_at = new DateTime(); }
public function isDeleted(): bool { return $this->deleted_at !== null; }
public function restore(): void { $this->deleted_at = null; }
}
class Article extends BaseModel {
use Timestampable, SoftDeletable; // 同時擁有時間戳與軟刪除
// 若兩個 Trait 有同名方法,需用 insteadof / as 解決衝突
}
衝突解決:
trait A { public function foo() { echo "A"; } }
trait B { public function foo() { echo "B"; } }
class C {
use A, B {
B::foo insteadof A; // 用 B 的 foo
A::foo as fooA; // 將 A 的 foo 別名為 fooA
}
}
將行為封裝在獨立物件,主類別持有並轉發呼叫。符合 SOLID 原則中的單一職責與依賴倒置。
// 行為介面
interface Notifier {
public function notify(string $message): void;
}
// 具體實作
class EmailNotifier implements Notifier {
public function notify(string $message): void { /* 寄信 */ }
}
class SmsNotifier implements Notifier {
public function notify(string $message): void { /* 發簡訊 */ }
}
// 主類別組合多個 Notifier
class OrderService {
public function __construct(
private Notifier $email,
private Notifier $sms,
private LoggerInterface $logger
) {}
public function placeOrder(Order $order): void {
// 業務邏輯...
$this->email->notify("訂單 {$order->id} 建立");
$this->sms->notify("您的訂單 {$order->id} 已確認");
$this->logger->info("Order placed", ['id' => $order->id]);
}
}
優點: 執行時可動態切換行為、易於單元測試(Mock Notifier)、無繼承鏈脆弱性。缺點: 樣板碼稍多、需手動轉發呼叫(可用 __call 自動委派)。
class Composite {
private array $components = [];
public function attach(object $component): void {
$this->components[spl_object_id($component)] = $component;
}
public function __call(string $method, array $args) {
foreach ($this->components as $comp) {
if (method_exists($comp, $method)) {
return $comp->$method(...$args); // 找到第一個實作即返回
}
}
throw new BadMethodCallException("No component implements $method");
}
}
// 使用
$svc = new Composite();
$svc->attach(new EmailNotifier());
$svc->attach(new SmsNotifier());
$svc->notify("Hello"); // 自動委派給 EmailNotifier->notify()
| 場景 | 推薦模式 | 理由 |
|---|---|---|
| 定義領域契約、多型替換 | Interface + Abstract Class | 語意清晰、IDE 支援、可靜態分析 |
| 跨類別共用「橫切關注點」(Log、Timestamp、SoftDelete、Serialization) | Trait | 零樣板碼、編譯期合併、無執行期開銷 |
| 行為需動態組合、替換、測試隔離、遵循組合優於繼承 | Composition / Delegation | 鬆耦合、執行期靈活、符合 SOLID |
| 需要「多重繼承」語意(如 Java 的 default method) | Trait + Interface | Trait 提供預設實作,Interface 定義型別 |
PHP 雖無原生多重繼承,但透過 Interface 定義契約、Trait 共用實作、Composition 組合行為 三大工具,反而能建構出比繼承更彈性、更可測試、更符合領域邏輯的架構。現代 PHP 開發(Laravel、Symfony、Doctrine)普遍採用「介面導向 + Trait 橫切 + 服務組合」的混合模式,建議根據業務語意而非「程式碼重用」來選擇。