介面與抽象類別是 PHP 物件導向設計的核心工具,雖有重疊但解決不同問題。本文透過電商商店場景深度對比,說明何時使用哪一種。
| 特性 | 介面 | 抽象類別 |
|---|---|---|
| 關鍵字 | interface | abstract class |
| 方法實作 | 不可有實作 (PHP 8.0 前) | 可有具體實作 |
| 屬性 | 不可有 (PHP 8.0+ 可有常數) | 可有屬性、常數 |
| 建構子 | 不可有 | 可有 |
| 繼承關係 | implements (多重實作) | extends (單一繼承) |
| 設計意圖 | 定義「契約/能力」 | 提供「基礎實作/模板」 |
| 存取修飾詞 | 隱含 public | public/protected/private |
<?php
// 契約:所有商店必須具備的能力
interface ShopInterface {
public function buy(int $productId, int $quantity): Order;
public function sell(int $productId, int $quantity): Receipt;
public function view(int $productId): Product;
public function getInventory(): array;
}
// 另一個契約:可計價
interface Pricable {
public function getPrice(): float;
public function setPrice(float $price): void;
}
// 多重實作:實體店同時實作商店介面與計價介面
class PhysicalStore implements ShopInterface, Pricable {
private array $inventory = [];
public function buy(int $productId, int $quantity): Order { /* ... */ }
public function sell(int $productId, int $quantity): Receipt { /* ... */ }
public function view(int $productId): Product { /* ... */ }
public function getInventory(): array { return $this->inventory; }
public function getPrice(): float { /* ... */ }
public function setPrice(float $price): void { /* ... */ }
}
?>
<?php
// 基礎商店:共用邏輯(連線、日誌、快取)
abstract class BaseShop {
protected PDO $db;
protected Logger $logger;
protected Cache $cache;
public function __construct(PDO $db, Logger $logger, Cache $cache) {
$this->db = $db;
$this->logger = $logger;
$this->cache = $cache;
}
// 具體實作:所有子類共用
protected function log(string $action, array $data): void {
$this->logger->info($action, $data + ['shop' => static::class]);
}
protected function getCached(string $key, callable $fallback, int $ttl = 300): mixed {
return $this->cache->remember($key, $fallback, $ttl);
}
// 模板方法:定義流程,子類實作細節
public function buy(int $productId, int $quantity): Order {
$this->validateStock($productId, $quantity);
$order = $this->createOrder($productId, $quantity);
$this->deductStock($productId, $quantity);
$this->log('buy', ['product' => $productId, 'qty' => $quantity]);
return $order;
}
// 抽象方法:強制子類實作
abstract protected function validateStock(int $productId, int $quantity): void;
abstract protected function createOrder(int $productId, int $quantity): Order;
abstract protected function deductStock(int $productId, int $quantity): void;
}
// 子類只需實作業務細節
class OnlineShop extends BaseShop {
protected function validateStock(int $productId, int $quantity): void {
$stock = $this->getCached("stock:$productId", fn() => $this->fetchStock($productId));
if ($stock < $quantity) throw new Exception('庫存不足');
}
protected function createOrder(int $productId, int $quantity): Order {
return Order::create(['product_id' => $productId, 'quantity' => $quantity]);
}
protected function deductStock(int $productId, int $quantity): void {
$this->db->exec("UPDATE products SET stock = stock - $quantity WHERE id = $productId");
$this->cache->forget("stock:$productId");
}
}
?>
<?php
// 介面定義對外契約
interface ShopInterface { /* ... */ }
// 抽象類別提供基礎建設
abstract class BaseShop { /* ... */ }
// 具體實作
class OnlineShop extends BaseShop implements ShopInterface { /* ... */ }
class PhysicalStore extends BaseShop implements ShopInterface { /* ... */ }
// 依賴介面而非具體類別(依賴倒置原則)
class OrderService {
public function __construct(
private ShopInterface $shop // 可注入任何實作
) {}
public function processBuy(int $productId, int $qty): Order {
return $this->shop->buy($productId, $qty);
}
}
?>
需要定義「必須做什麼」嗎?
├─ 是 → 使用 interface
│ └─ 需要多重實作? → interface (支援 implements A, B, C)
│
└─ 需要提供「共用怎麼做」嗎?
├─ 是 → 使用 abstract class
│ ├─ 有建構子邏輯? → abstract class
│ ├─ 有 protected 共用方法? → abstract class
│ └─ 模板方法模式? → abstract class
│
└─ 兩者都需要? → interface + abstract class 組合
public const 常數default 實作(public function foo(): int { return 1; })mixed 型別提示支援介面/抽象類別介面定義「外部契約」,抽象類別提供「內部骨架」。善用兩者組合,配合依賴注入,能建立高內聚、低耦合、可測試的系統架構。