PHP 5 引入完整物件模型,雖不支援原生多重繼承,但透過介面與組合可優雅實現多態與程式碼復用。本文以電商折扣系統為例,示範兩種核心模式的實戰應用。
定義契約介面,多種實現類互換,消費端針對介面程式設計而非具體類。
<?php
// 1. 定義契約
interface User
{
public function getName(): string;
public function setName(string $name): void;
public function getDiscount(): float;
}
// 2. 抽象基類封裝共同邏輯
abstract class AbstractUser implements User
{
protected string $name = '';
protected float $discount = 1.0;
protected string $grade = 'Base';
public function __construct(string $name)
{
$this->setName($name);
}
public function getName(): string { return $this->name; }
public function setName(string $name): void { $this->name = $name; }
public function getDiscount(): float { return $this->discount; }
public function getGrade(): string { return $this->grade; }
}
// 3. 具體實現
class NormalUser extends AbstractUser
{
protected float $discount = 1.0;
protected string $grade = 'Normal';
}
class VipUser extends AbstractUser
{
protected float $discount = 0.8;
protected string $grade = 'VIP';
}
class InnerUser extends AbstractUser
{
protected float $discount = 0.7;
protected string $grade = 'Internal';
}
// 4. 消費端:結算服務依賴介面 User
class OrderSettlement
{
public static function calculateFinalPrice(User $user, Product $product, int $quantity): float
{
return $user->getDiscount() * $product->getPrice() * $quantity;
}
}
// 5. 產品介面
interface Product
{
public function getName(): string;
public function getPrice(): float;
}
class Book implements Product
{
public function __construct(private string $name, private float $price = 100) {}
public function getName(): string { return $this->name; }
public function getPrice(): float { return $this->price; }
}
// === 使用 ===
$book = new Book('設計模式');
$user = new NormalUser('Tom');
$price = OrderSettlement::calculateFinalPrice($user, $book, 10);
echo "用戶: {$user->getName()} ({$user->getGrade()})\n";
echo "折扣: {$user->getDiscount()}\n";
echo "總價: {$price}\n";
當類別需要複用多個來源的行為 (如:可記錄日誌 + 可序列化 + 可快取),透過「持有介面實例」替代「繼承多個父類」。
<?php
// 行為介面
interface Logger { public function log(string $msg): void; }
interface Cacheable { public function getCacheKey(): string; public function getTtl(): int; }
interface Serializable { public function serialize(): string; public function unserialize(string $data): void; }
// 具體行為實現
class FileLogger implements Logger {
public function log(string $msg): void { file_put_contents('app.log', $msg . PHP_EOL, FILE_APPEND); }
}
class RedisCache implements Cacheable {
public function getCacheKey(): string { return 'cache:' . spl_object_id($this); }
public function getTtl(): int { return 3600; }
}
class JsonSerializer implements Serializable {
public function serialize(): string { return json_encode($this); }
public function unserialize(string $data): void { /* ... */ }
}
// 主類別:組合三種行為
class ProductEntity implements Cacheable, Serializable
{
// 組合:持有介面實例
private Logger $logger;
private Cacheable $cache;
private Serializable $serializer;
public function __construct(
private string $name,
private float $price,
Logger $logger = null,
Cacheable $cache = null,
Serializable $serializer = null
) {
$this->logger = $logger ?? new FileLogger();
$this->cache = $cache ?? new RedisCache();
$this->serializer = $serializer ?? new JsonSerializer();
}
// 委派給組合物件
public function log(string $msg): void { $this->logger->log($msg); }
public function getCacheKey(): string { return $this->cache->getCacheKey(); }
public function getTtl(): int { return $this->cache->getTtl(); }
public function serialize(): string { return $this->serializer->serialize(); }
public function unserialize(string $data): void { $this->serializer->unserialize($data); }
// 業務方法
public function applyDiscount(float $rate): void {
$this->price *= $rate;
$this->log("Price updated to {$this->price} for {$this->name}");
}
}
// === 使用 ===
$product = new ProductEntity('筆電', 30000);
$product->applyDiscount(0.9);
echo "Cache Key: {$product->getCacheKey()}\n";
echo "Serialized: {$product->serialize()}\n";
| 維度 | 介面多態 | 組合模擬多重繼承 |
|---|---|---|
| 核心目的 | 「是一種」關係:不同實現互換 | 「擁有」關係:複用多組獨立行為 |
| 耦合度 | 低 (依賴介面) | 低 (依賴介面,構造函數注入) |
| 擴展性 | 新增實現類不修改消費端 | 新增行為介面+實現,主類無需修改 |
| 適用場景 | 策略模式、支付渠道、用戶分級、存儲驅動 | 橫切關注點 (日誌、快取、序列化、權限、事件) |
| PHP 8+ 增強 | interface 可定義 public function 預設實現 | trait 可替代部分組合場景 |
readonly、Constructor Property Promotion 簡化組合類樣板代碼。掌握介面多態與組合,即掌握了 PHP 物件導向設計的靈魂,能構建高內聚、低耦合、易測試、可演進的企業級應用架構。