適配器模式將一個類的接口轉換為客戶端期望的另一個接口,使原本不兼容的類可以協同工作。它是整合第三方支付、物流、存儲、郵件等異構服務的標準模式。本文展示類適配器(繼承)與對象適配器(組合)兩種形式,並給出多適配器統一管理的工廠模式結合方案。
<?php
// 1. 目標介面:客戶端期望的統一協議
interface PaymentGateway {
public function charge(float $amount, string $currency, array $options): PaymentResult;
public function refund(string $transactionId, float $amount): RefundResult;
public function getTransaction(string $id): Transaction;
}
// 2. 被適配者:第三方 SDK 真實接口(不可修改)
class StripeSdk {
public function createCharge(array $params): \Stripe\Charge { /* ... */ }
public function createRefund(array $params): \Stripe\Refund { /* ... */ }
public function retrieveCharge(string $id): \Stripe\Charge { /* ... */ }
}
class PayPalSdk {
public function executePayment(array $data): \PayPal\Payment { /* ... */ }
public function refundSale(string $saleId, array $amount): \PayPal\Refund { /* ... */ }
public function getSale(string $id): \PayPal\Sale { /* ... */ }
}
// 3. 統一數據傳輸對象(DTO)
class PaymentResult { public bool $success; public string $transactionId; public string $rawResponse; }
class RefundResult { public bool $success; public string $refundId; }
class Transaction { public string $id; public float $amount; public string $status; public string $currency; }
<?php
class StripeAdapter implements PaymentGateway {
private StripeSdk $sdk;
public function __construct(StripeSdk $sdk) {
$this->sdk = $sdk;
}
public function charge(float $amount, string $currency, array $options): PaymentResult {
try {
$charge = $this->sdk->createCharge([
'amount' => (int)($amount * 100), // Stripe 單位:分
'currency' => strtolower($currency),
'source' => $options['token'] ?? '',
'description' => $options['description'] ?? '',
'metadata' => $options['metadata'] ?? [],
]);
return new PaymentResult([
'success' => true,
'transactionId' => $charge->id,
'rawResponse' => json_encode($charge->jsonSerialize()),
]);
} catch (\Stripe\Exception\CardException $e) {
return new PaymentResult(['success' => false, 'transactionId' => '', 'rawResponse' => $e->getMessage()]);
}
}
public function refund(string $transactionId, float $amount): RefundResult {
$refund = $this->sdk->createRefund([
'charge' => $transactionId,
'amount' => (int)($amount * 100),
]);
return new RefundResult(['success' => true, 'refundId' => $refund->id]);
}
public function getTransaction(string $id): Transaction {
$charge = $this->sdk->retrieveCharge($id);
return new Transaction([
'id' => $charge->id,
'amount' => $charge->amount / 100,
'status' => $charge->status,
'currency' => strtoupper($charge->currency),
]);
}
}
class PayPalAdapter implements PaymentGateway {
private PayPalSdk $sdk;
public function __construct(PayPalSdk $sdk) {
$this->sdk = $sdk;
}
public function charge(float $amount, string $currency, array $options): PaymentResult {
$payment = $this->sdk->executePayment([
'amount' => ['total' => number_format($amount, 2), 'currency' => $currency],
'payer' => ['payment_method' => 'paypal'],
'redirect_urls' => $options['redirect_urls'] ?? [],
]);
return new PaymentResult([
'success' => $payment->getState() === 'approved',
'transactionId' => $payment->getId(),
'rawResponse' => json_encode($payment->toArray()),
]);
}
public function refund(string $transactionId, float $amount): RefundResult {
$refund = $this->sdk->refundSale($transactionId, [
'amount' => ['total' => number_format($amount, 2), 'currency' => 'USD'],
]);
return new RefundResult(['success' => true, 'refundId' => $refund->getId()]);
}
public function getTransaction(string $id): Transaction {
$sale = $this->sdk->getSale($id);
return new Transaction([
'id' => $sale->getId(),
'amount' => (float)$sale->getAmount()->getTotal(),
'status' => $sale->getState(),
'currency' => $sale->getAmount()->getCurrency(),
]);
}
}
<?php
// 假設舊系統有一個類,需適配為新介面
class LegacyLogger {
public function writeLog(string $level, string $message): void { /* ... */ }
}
// 新介面
interface Logger {
public function info(string $message): void;
public function error(string $message): void;
public function debug(string $message): void;
}
// 類適配器:繼承舊類 + 實現新介面
class LegacyLoggerAdapter extends LegacyLogger implements Logger {
public function info(string $message): void { $this->writeLog('INFO', $message); }
public function error(string $message): void { $this->writeLog('ERROR', $message); }
public function debug(string $message): void { $this->writeLog('DEBUG', $message); }
}
<?php
class PaymentGatewayFactory {
private array $adapters = [];
public function __construct() {
// 註冊可用適配器(懶加載)
$this->adapters = [
'stripe' => fn() => new StripeAdapter(new StripeSdk()),
'paypal' => fn() => new PayPalAdapter(new PayPalSdk()),
'mock' => fn() => new MockAdapter(), // 測試用
];
}
public function create(string $gateway): PaymentGateway {
if (!isset($this->adapters[$gateway])) {
throw new InvalidArgumentException("Unsupported gateway: $gateway");
}
return ($this->adapters[$gateway])();
}
public function getAvailable(): array {
return array_keys($this->adapters);
}
}
// 使用
$factory = new PaymentGatewayFactory();
$gateway = $factory->create(config('payment.default')); // 從配置讀取
$result = $gateway->charge(99.99, 'USD', ['token' => 'tok_visa']);
// 多網關降級重試
$gateways = ['stripe', 'paypal'];
foreach ($gateways as $gw) {
try {
$gateway = $factory->create($gw);
$result = $gateway->charge($amount, $currency, $options);
if ($result->success) break;
} catch (Throwable $e) {
Log::warning("Gateway $gw failed: " . $e->getMessage());
}
}
| 模式 | 意圖 | 接口關係 | 典型場景 |
|---|---|---|---|
| 適配器 | 接口轉換(不兼容→兼容) | Target ≠ Adaptee | 第三方 SDK 整合、遺留系統改造 |
| 外觀 | 簡化複雜子系統接口 | Facade 封裝多個類 | 提供簡單入口隱藏內部複雜度 |
| 裝飾器 | 動態添加職責 | Decorator = Component | 日誌、緩存、授權、壓縮等橫切關注點 |
<?php
interface StorageAdapter {
public function put(string $path, string $content, array $options = []): string; // 返回訪問 URL
public function get(string $path): string;
public function delete(string $path): bool;
public function exists(string $path): bool;
public function url(string $path): string;
}
class LocalStorageAdapter implements StorageAdapter { /* ... */ }
class QiniuStorageAdapter implements StorageAdapter { /* ... */ }
class S3StorageAdapter implements StorageAdapter { /* ... */ }
// Laravel 文件系統即採用此模式
// config/filesystems.php
// 'disks' => [
// 'local' => ['driver' => 'local', 'root' => storage_path('app')],
// 's3' => ['driver' => 's3', 'key' => env('AWS_KEY'), ...],
// 'qiniu' => ['driver' => 'qiniu', 'access_key' => ..., 'bucket' => ...],
// ]
// 使用:Storage::disk('s3')->put('file.txt', $content);
總結:適配器模式是系統集成的通用適配層。通過「目標介面 + 適配器實現 + 工廠註冊」三層結構,將第三方 SDK 的差異性封裝在適配器內部,業務代碼面向統一介面編程,實現依賴倒置、開閉原則、單一職責。無論是支付、存儲、郵件、短信、地圖、推送,統統適用此模式。