PHP项目装饰器与适配器

wen PHP项目 3

本文目录导读:

PHP项目装饰器与适配器

  1. 核心定义
  2. PHP 项目中的典型场景
  3. 关键区别(面试常问)
  4. 如何在 PHP 项目中做选择?
  5. 常见陷阱

在 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 框架下的具体实现模板,我可以帮你进一步展开。

抱歉,评论功能暂时关闭!