電商系統中,產品類型繁多(實體商品、虛擬下載、服務訂閱、組合包),每種類型的「顯示詳情」、「價格計算」、「庫存檢查」邏輯截然不同。傳統 if/else 或 switch 導致控制器膨脹、違反開放封閉原則。
<?php
// 1. 定義介面契約
interface Product {
public function display(): string;
public function getPrice(): float;
public function checkStock(): bool;
}
// 2. 基類封裝共同屬性
abstract class BaseProduct implements Product {
protected int $id;
protected string $name;
protected float $price;
public function __construct(int $id, string $name, float $price) {
$this->id = $id; $this->name = $name; $this->price = $price;
}
public function getPrice(): float { return $this->price; }
}
// 3. 具體實現
class PhysicalProduct extends BaseProduct {
private int $stock;
public function __construct(int $id, string $name, float $price, int $stock) {
parent::__construct($id, $name, $price);
$this->stock = $stock;
}
public function display(): string {
return "<div><h3>{$this->name}</h3>價格: {$this->price} 庫存: {$this->stock}</div>";
}
public function checkStock(): bool { return $this->stock > 0; }
}
class DigitalProduct extends BaseProduct {
private string $downloadUrl;
public function __construct(int $id, string $name, float $price, string $url) {
parent::__construct($id, $name, $price);
$this->downloadUrl = $url;
}
public function display(): string {
return "<div><h3>{$this->name}</h3>價格: {$this->price} <a href='{$this->downloadUrl}'>立即下載</a></div>";
}
public function checkStock(): bool { return true; } // 永不缺貨
}
// 4. 工廠方法(可擴展為註冊表模式)
class ProductFactory {
private static array $typeMap = [
'physical' => PhysicalProduct::class,
'digital' => DigitalProduct::class,
// 新增類型只需在這裡註冊,無需修改控制器
];
public static function create(string $type, array $data): ?Product {
$class = self::$typeMap[$type] ?? null;
if (!$class) return null;
return new $class(...$data); // PHP 8.0+ 解包參數
}
}
// 5. 控制器極度精簡
$productType = $_GET['type'] ?? 'physical'; // 來自資料庫 type 欄位
$productData = [1, 'PHP 進階教程', 299.0, 'https://cdn.example.com/php-advanced.pdf'];
$product = ProductFactory::create($productType, $productData);
if ($product) {
echo $product->display();
echo "庫存狀態: " . ($product->checkStock() ? '有貨' : '缺貨');
} else {
echo "未知產品類型";
}
?>
| 傳統 if/else | 動態多型 |
|---|---|
| 控制器包含所有類型邏輯,千行巨型函數 | 控制器零業務邏輯,僅負責協調 |
| 新增類型需修改控制器、測試全流程 | 新增類別 + 工廠註冊,零侵入現有代碼 |
| 單元測試困難,需模擬請求上下文 | 每個類別獨立測試,邊界清晰 |
| 團隊協作衝突:所有人改同一檔案 | 各自負責各自類別,Git 衝突極少 |
<?php
class PrototypeProduct implements Product {
public array $tags = [];
public function __clone() {
// 深複製:確保陣列/物件屬性不共享引用
$this->tags = array_map(fn($t) => clone $t, $this->tags);
}
}
$original = new PrototypeProduct();
$original->tags = [new Tag('hot'), new Tag('sale')];
$copy = clone $original; // 呼叫 __clone()
$copy->tags[] = new Tag('new');
var_dump($original->tags); // 仍只有 2 個,未受影響
?>
clone 為淺複製:標量複製,陣列/物件屬性共享引用。__clone() 定義深複製邏輯:避免副作用,符合值物件語義。<?php
interface ProductRenderer {
public function render(Product $product): string;
}
class ZhTwRenderer implements ProductRenderer { /* 繁中渲染 */ }
class EnUsRenderer implements ProductRenderer { /* 英文渲染 */ }
// 根據用戶語言動態選擇渲染器
$rendererClass = 'Renderer\\' . str_replace('-', '', $_SESSION['locale']) . 'Renderer';
$renderer = new $rendererClass();
echo $renderer->render($product);
?>
__clone 正確性、工廠註冊完整性、介面契約遵守。「根據資料庫 type 欄位動態實例化類別」是 PHP 動態特性的經典應用,配合介面契約與工廠模式,能將大型專案的條件分支複雜度從 O(N) 降為 O(1) 維護成本。物件複製機制則為原型模式、值物件、不可變物件提供語言級支援。掌握這兩大技法,是邁向 PHP 高階架構師的關鍵里程碑。