PHP设计模式策略与适配器实战
PHP设计模式策略与适配器实战
策略模式和适配器模式在实际项目中非常常用。策略模式让你能动态切换算法,适配器模式让不兼容的接口能协同工作。今天就用实战例子说明这两种模式。
策略模式把算法封装成独立的类,客户端可以根据需要选择不同的算法。最常见的例子是支付方式的选择。
```php
interface PaymentGateway
{
public function pay(float $amount, array $params = []): array;
public function refund(string $transactionId): array;
public function getName(): string;
}
class AlipayGateway implements PaymentGateway
{
private string $appId;
private string $privateKey;
public function __construct(string $appId, string $privateKey)
{
$this->appId = $appId;
$this->privateKey = $privateKey;
}
public function pay(float $amount, array $params = []): array
{
return [
'channel' => 'alipay',
'trade_no' => 'ALI' . date('Ymd') . uniqid(),
'amount' => $amount,
'status' => 'success',
'timestamp' => date('Y-m-d H:i:s'),
];
}
public function refund(string $transactionId): array
{
return [
'channel' => 'alipay',
'refund_no' => 'REF' . $transactionId,
'status' => 'success',
];
}
public function getName(): string { return '支付宝'; }
}
class WechatGateway implements PaymentGateway
{
private string $appId;
private string $mchId;
public function __construct(string $appId, string $mchId)
{
$this->appId = $appId;
$this->mchId = $mchId;
}
public function pay(float $amount, array $params = []): array
{
return [
'channel' => 'wechat',
'trade_no' => 'WX' . date('Ymd') . uniqid(),
'amount' => $amount,
'status' => 'success',
'openid' => $params['openid'] ?? '',
];
}
public function refund(string $transactionId): array
{
return [
'channel' => 'wechat',
'refund_no' => 'REF' . $transactionId,
'status' => 'success',
];
}
public function getName(): string { return '微信支付'; }
}
class PaymentContext
{
private ?PaymentGateway $gateway = null;
public function __construct(PaymentGateway $gateway)
{
$this->gateway = $gateway;
}
public function setGateway(PaymentGateway $gateway): void
{
$this->gateway = $gateway;
}
public function executePayment(float $amount, array $params = []): array
{
if ($this->gateway === null) {
throw new RuntimeException('未设置支付网关');
}
$result = $this->gateway->pay($amount, $params);
$this->logPayment($result);
return $result;
}
private function logPayment(array $result): void
{
$log = sprintf(
"[%s] 支付: %s 交易号: %s 金额: %.2f 状态: %s\n",
date('Y-m-d H:i:s'),
$result['channel'],
$result['trade_no'],
$result['amount'],
$result['status']
);
file_put_contents('/tmp/payments.log', $log, FILE_APPEND);
}
}
$alipay = new AlipayGateway('app123', 'key456');
$payment = new PaymentContext($alipay);
$result = $payment->executePayment(99.99);
print_r($result);
// 切换到微信
$payment->setGateway(new WechatGateway('wx_app', 'mch001'));
$result2 = $payment->executePayment(199.99, ['openid' => 'o12345']);
print_r($result2);
?>
```
数据导出也是策略模式的常见应用。不同的导出格式对应不同的策略。
```php
interface ExportStrategy
{
public function export(array $data): string;
public function getContentType(): string;
public function getExtension(): string;
}
class JsonExport implements ExportStrategy
{
public function export(array $data): string
{
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
public function getContentType(): string { return 'application/json'; }
public function getExtension(): string { return 'json'; }
}
class CsvExport implements ExportStrategy
{
public function export(array $data): string
{
if (empty($data)) return '';
$output = fopen('php://temp', 'r+');
fputcsv($output, array_keys($data[0]));
foreach ($data as $row) {
fputcsv($output, $row);
}
rewind($output);
$content = stream_get_contents($output);
fclose($output);
return $content;
}
public function getContentType(): string { return 'text/csv'; }
public function getExtension(): string { return 'csv'; }
}
class XmlExport implements ExportStrategy
{
public function export(array $data): string
{
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->formatOutput = true;
$root = $doc->createElement('data');
foreach ($data as $item) {
$element = $doc->createElement('item');
foreach ($item as $key => $value) {
$child = $doc->createElement($key);
$child->appendChild($doc->createTextNode((string)$value));
$element->appendChild($child);
}
$root->appendChild($element);
}
$doc->appendChild($root);
return $doc->saveXML();
}
public function getContentType(): string { return 'application/xml'; }
public function getExtension(): string { return 'xml'; }
}
class DataExporter
{
private ExportStrategy $strategy;
public function __construct(ExportStrategy $strategy)
{
$this->strategy = $strategy;
}
public function export(array $data, string $filename): void
{
$content = $this->strategy->export($data);
$fullFilename = $filename . '.' . $this->strategy->getExtension();
header('Content-Type: ' . $this->strategy->getContentType());
header('Content-Disposition: attachment; filename="' . $fullFilename . '"');
header('Content-Length: ' . strlen($content));
echo $content;
}
public function exportToFile(array $data, string $path): string
{
$content = $this->strategy->export($data);
$fullPath = $path . '.' . $this->strategy->getExtension();
file_put_contents($fullPath, $content);
return $fullPath;
}
}
$data = [
['name' => '张三', 'age' => 28, 'email' => 'zhangsan@test.com'],
['name' => '李四', 'age' => 35, 'email' => 'lisi@test.com'],
];
$exporter = new DataExporter(new JsonExport());
$path = $exporter->exportToFile($data, '/tmp/export');
echo "导出到: $path\n";
$exporter2 = new DataExporter(new CsvExport());
$path2 = $exporter2->exportToFile($data, '/tmp/export');
echo "导出到: $path2\n";
?>
```
适配器模式用来让不兼容的接口协同工作。在整合第三方库时特别有用。
```php
// 目标接口
interface LogInterface
{
public function info(string $message): void;
public function error(string $message): void;
public function warning(string $message): void;
}
// 旧系统实现的日志
class SimpleFileLogger
{
private string $path;
public function __construct(string $path)
{
$this->path = $path;
}
public function write(string $level, string $message): void
{
$line = sprintf(
"[%s] %s: %s\n",
date('Y-m-d H:i:s'),
$level,
$message
);
file_put_contents($this->path, $line, FILE_APPEND);
}
}
// 适配器
class SimpleLoggerAdapter implements LogInterface
{
private SimpleFileLogger $logger;
public function __construct(SimpleFileLogger $logger)
{
$this->logger = $logger;
}
public function info(string $message): void
{
$this->logger->write('INFO', $message);
}
public function error(string $message): void
{
$this->logger->write('ERROR', $message);
}
public function warning(string $message): void
{
$this->logger->write('WARNING', $message);
}
}
$logger = new SimpleLoggerAdapter(new SimpleFileLogger('/tmp/app.log'));
$logger->info('用户登录成功');
$logger->error('数据库连接失败');
echo "日志已写入\n";
?>
```
策略模式和适配器模式的核心思想都是面向接口编程。策略模式是"有不同的实现,根据情况选用",适配器模式是"接口不兼容,包一层转接"。理解了这两种模式,代码的扩展性和灵活性会提升很多。
