本文目录导读:

在 PHP 项目中,装饰器模式 和 适配器模式 都是结构型设计模式,但它们的目的和使用场景有本质区别。
下面从核心定义、PHP 示例、典型场景以及关键区别来详细说明。
核心定义
-
适配器模式(Adapter):
- 目标: 让原本不兼容的接口能够协同工作,它把一个类的接口转换成客户端期望的另一个接口。
- 本质: “接口转换器” 或 “封装器”。
- 关注点: 解决接口不匹配问题,重写、适配已有接口。
-
装饰器模式(Decorator):
- 目标: 动态地给一个对象添加额外的职责或行为,而无需修改其原始类。
- 本质: “包装器” + “增强器”。
- 关注点: 解决功能扩展问题,不改变接口,只增加行为。
PHP 项目中的典型场景
适配器 —— 集成第三方支付
假设你的系统设计了一个统一的支付接口 PaymentInterface,但你要集成的支付宝 SDK 使用的是完全不同的一套方法。
<?php
// --- 你的系统标准接口 ---
interface PaymentInterface {
public function pay(float $amount): void;
}
// --- 系统内部类(使用标准接口) ---
class OrderProcessor {
public function processOrder(PaymentInterface $payment, float $amount) {
// 这里只依赖 PaymentInterface
$payment->pay($amount);
}
}
// --- 第三方支付宝类(不兼容的接口) ---
class AlipaySDK {
public function sendPayment(string $orderId, float $totalAmount): string {
// 实际调用支付宝 API
echo "支付宝支付:订单 {$orderId},金额 {$totalAmount}\n";
return "success";
}
}
// --- 适配器:将 AlipaySDK 转换成 PaymentInterface ---
class AlipayAdapter implements PaymentInterface {
private AlipaySDK $alipay;
public function __construct(AlipaySDK $alipay) {
$this->alipay = $alipay;
}
public function pay(float $amount): void {
// 适配:转换方法名和参数
$orderId = 'ORD' . uniqid();
$this->alipay->sendPayment($orderId, $amount);
}
}
// --- 使用 ---
$alipay = new AlipaySDK();
$adapter = new AlipayAdapter($alipay);
$processor = new OrderProcessor();
$processor->processOrder($adapter, 99.99); // 输出:支付宝支付:订单...,金额 99.99
装饰器 —— 消息通知增强
假设你有一个基础的消息发送器,现在希望在发送消息前/后进行一些增强(如记录日志、添加签名、或者推送渠道切换)。
<?php
// --- 基础接口 ---
interface NotifierInterface {
public function send(string $message): void;
}
// --- 核心组件 ---
class BasicNotifier implements NotifierInterface {
public function send(string $message): void {
echo "发送消息: {$message}\n";
}
}
// --- 基础装饰器(抽象类,也可以直接类实现接口) ---
abstract class NotifierDecorator implements NotifierInterface {
protected NotifierInterface $wrapped;
public function __construct(NotifierInterface $notifier) {
$this->wrapped = $notifier;
}
// 抽象类中通常不重写 send,交由子类实现
abstract public function send(string $message): void;
}
// --- 具体装饰器 1:添加日志 ---
class LoggingDecorator extends NotifierDecorator {
public function send(string $message): void {
echo "[日志] 记录发送操作: " . date('Y-m-d H:i:s') . "\n";
$this->wrapped->send($message);
}
}
// --- 具体装饰器 2:添加签名 ---
class SignatureDecorator extends NotifierDecorator {
public function send(string $message): void {
$signedMessage = $message . "\n--- 由系统自动生成 ---";
$this->wrapped->send($signedMessage);
}
}
// --- 使用 ---
$notifier = new BasicNotifier();
// 先加日志,再加签名
$notifier = new LoggingDecorator($notifier);
$notifier = new SignatureDecorator($notifier);
$notifier->send("Hello World");
// 输出:
// [日志] 记录发送操作: 2025-03-25 10:00:00
// 发送消息: Hello World
// --- 由系统自动生成 ---
关键区别(面试常问)
| 特征 | 适配器模式 | 装饰器模式 |
|---|---|---|
| 目的 | 接口转换,让不兼容的类协作 | 功能增强,动态添加新职责 |
| 输入/输出 | 输入是旧接口,输出是新接口 | 输入和输出接口相同(不改变签名) |
| 对象数量 | 通常一对一(一个适配器适配一个被适配者) | 可以层层嵌套(多个装饰器叠加) |
| 包装关系 | 包装后的对象通常不再具备原始类型(只暴露目标接口) | 包装后的对象仍然是原始类型(符合 Liskov 替换原则) |
| 典型应用 | 集成第三方库、老系统改造 | 日志、缓存、权限、AOP 横向切面 |
如何在 PHP 项目中做选择?
| 情况 | 推荐模式 |
|---|---|
有一个旧类(如 OldLogger),你需要让它符合系统新的 LoggerInterface |
✅ 适配器 |
有一个 HttpClient,你想在每次请求前自动添加认证 Token(不影响原接口) |
✅ 装饰器 |
| 集成微信支付,其回调格式与系统要求不一样 | ✅ 适配器 |
| 对数据库查询结果做缓存、或对控制器响应做格式化 | ✅ 装饰器 |
常见陷阱
- 装饰器可能改变原始类型(错误做法): 如果装饰器返回的不是同一个接口类型,那就变成了适配器或代理,失去了装饰器的可叠加特性。
- 适配器不应增加新行为: 适配器主要做翻译/转换,不应添加功能(如缓存、日志),如果既转换接口又增强功能,请分层:内部用适配器,外部用装饰器。
- 过度使用装饰器: 太多层装饰会导致调试困难,可以考虑用 Pipeline 模式(管道模式)替代多层装饰器。
如果你需要完整的代码示例文件或 Laravel/Symfony 框架下的具体实现模板,我可以帮你进一步展开。