PHP 适配器模式

概述

适配器模式(Adapter Pattern)是一种结构型设计模式,其核心思想是将一个类(或对象)的接口转换成客户端期望的另一个接口,使原本因接口不兼容而无法协同工作的类能够无缝配合。该模式扮演“转换器”的角色,在不修改原有类代码的前提下,解决接口兼容性问题,同时保留原有类的核心功能,实现代码的复用与扩展。

适配器模式遵循“开放-封闭原则”,通过引入适配器类隔离客户端与原始类的直接依赖,当原始类接口发生变化时,仅需修改适配器即可,无需调整客户端代码,大幅提升系统的灵活性和可维护性。

核心价值

核心要点 详细说明
解决接口兼容 统一客户端调用接口,使不兼容的原始类(旧系统组件、第三方库)能够与客户端期望的接口适配,消除接口差异导致的协作障碍
复用现有代码 无需修改原始类的核心逻辑(如老系统稳定代码、第三方库源码),通过适配器复用已有功能,避免重复开发,降低改造风险
降低系统耦合 客户端仅依赖统一的目标接口,与原始类解耦,原始类的接口变更仅需调整适配器,无需影响客户端和其他模块
灵活扩展适配 可通过新增适配器类支持不同的原始类,无需修改现有适配逻辑,符合“开放-封闭原则”,适配多种场景需求

适用场景

场景类型 具体说明
老系统升级改造 老系统组件接口与新系统接口规范不一致,需复用老系统功能但无法修改源码(如遗留项目接口适配新框架接口)
第三方库集成 引入的第三方类库接口与项目现有接口不兼容(如支付网关SDK、数据解析库的接口与项目统一接口规范冲突)
多版本接口兼容 同一功能存在多个版本接口(如V1、V2版本API),需通过适配器统一客户端调用方式,屏蔽版本差异
代码重构过渡 重构过程中需逐步替换旧接口,适配器可作为过渡层,使新老接口并行运行,降低重构风险

注意事项

注意点 具体说明
明确适配边界 适配器仅负责接口转换,不新增核心业务逻辑,避免将适配逻辑与业务逻辑混淆,导致职责边界模糊
优先选择对象适配器 适配器模式分为类适配器(继承原始类)和对象适配器(组合原始类),对象适配器灵活性更高(支持适配多个原始类),且避免继承带来的耦合,推荐优先使用
避免过度适配 若接口差异极小(如仅方法名不同且逻辑一致),可直接通过简单封装解决,无需引入适配器模式增加系统复杂度
确保目标接口稳定 客户端依赖的目标接口应相对稳定,若目标接口频繁变更,会导致适配器需反复修改,失去适配意义

完整代码

以“支付接口适配”为业务场景,构建适配器模式实现:现有系统期望通过统一的 PaymentAdapterInterface 接口调用支付功能,但需集成的第三方支付库(支付宝、微信支付)接口不一致,通过适配器将不同支付接口转换为统一格式。

<?php
declare(strict_types = 1);

/**
 * 目标接口:系统期望的统一支付接口规范
 */
interface PaymentAdapterInterface
{
    /**
     * 发起支付
     * @param float $amount 支付金额
     * @param string $orderNo 订单号
     * @return string 支付结果(含支付链接或状态信息)
     */
    public function pay(float $amount, string $orderNo): string;

    /**
     * 查询支付状态
     * @param string $orderNo 订单号
     * @return string 支付状态(未支付/支付成功/支付失败)
     */
    public function queryStatus(string $orderNo): string;
}

/**
 * 原始类1:第三方支付宝支付(接口与目标接口不兼容)
 */
class Alipay
{
    /**
     * 支付宝发起支付(方法名、参数与目标接口不同)
     * @param array $params 支付参数(含金额、订单号等)
     * @return string 支付宝支付链接
     */
    public function createAlipayOrder(array $params): string
    {
        $amount = $params['total_amount'] ?? 0.0;
        $orderNo = $params['out_trade_no'] ?? '';
        return sprintf("支付宝支付链接生成成功:https://pay.alipay.com/?order=%s&amount=%.2f", $orderNo, $amount);
    }

    /**
     * 支付宝查询支付状态(方法名与目标接口不同)
     * @param string $tradeNo 订单号
     * @return string 支付状态
     */
    public function getAlipayOrderStatus(string $tradeNo): string
    {
        // 模拟查询逻辑:随机返回支付状态
        $statusList = ['未支付', '支付成功', '支付失败'];
        return sprintf("支付宝订单【%s】状态:%s", $tradeNo, $statusList[array_rand($statusList)]);
    }
}

/**
 * 原始类2:第三方微信支付(接口与目标接口不兼容)
 */
class WechatPay
{
    /**
     * 微信发起支付(方法名、返回格式与目标接口不同)
     * @param string $orderId 订单号
     * @param float $money 支付金额
     * @return array 微信支付结果(含状态码和支付链接)
     */
    public function wechatPay(string $orderId, float $money): array
    {
        return [
            'code' => 200,
            'message' => '支付链接生成成功',
            'data' => [
                'pay_url' => sprintf("https://pay.weixin.qq.com/?order=%s&amount=%.2f", $orderId, $money),
                'order_no' => $orderId
            ]
        ];
    }

    /**
     * 微信查询支付状态(返回格式与目标接口不同)
     * @param string $orderId 订单号
     * @return array 微信支付状态结果
     */
    public function queryWechatPayStatus(string $orderId): array
    {
        $statusMap = [0 => '未支付', 1 => '支付成功', 2 => '支付失败'];
        $statusCode = rand(0, 2);
        return [
            'code' => 200,
            'message' => '查询成功',
            'data' => [
                'order_no' => $orderId,
                'status' => $statusMap[$statusCode],
                'status_code' => $statusCode
            ]
        ];
    }
}

/**
 * 具体适配器1:支付宝适配器(将支付宝接口适配为目标接口)
 */
class AlipayAdapter implements PaymentAdapterInterface
{
    /**
     * 持有原始支付宝对象(对象适配器:组合方式)
     * @var Alipay
     */
    private Alipay $alipay;

    public function __construct(Alipay $alipay)
    {
        $this->alipay = $alipay;
    }

    public function pay(float $amount, string $orderNo): string
    {
        // 适配参数格式:将目标接口的参数转换为支付宝所需格式
        $params = [
            'out_trade_no' => $orderNo,
            'total_amount' => $amount
        ];
        // 调用支付宝原始方法,返回统一格式结果
        return $this->alipay->createAlipayOrder($params);
    }

    public function queryStatus(string $orderNo): string
    {
        // 调用支付宝原始查询方法,适配返回格式
        return $this->alipay->getAlipayOrderStatus($orderNo);
    }
}

/**
 * 具体适配器2:微信支付适配器(将微信支付接口适配为目标接口)
 */
class WechatPayAdapter implements PaymentAdapterInterface
{
    /**
     * 持有原始微信支付对象(对象适配器:组合方式)
     * @var WechatPay
     */
    private WechatPay $wechatPay;

    public function __construct(WechatPay $wechatPay)
    {
        $this->wechatPay = $wechatPay;
    }

    public function pay(float $amount, string $orderNo): string
    {
        // 调用微信支付原始方法,适配参数顺序和返回格式
        $result = $this->wechatPay->wechatPay($orderNo, $amount);
        return sprintf("微信支付链接生成成功:%s", $result['data']['pay_url']);
    }

    public function queryStatus(string $orderNo): string
    {
        // 调用微信支付原始查询方法,适配返回格式
        $result = $this->wechatPay->queryWechatPayStatus($orderNo);
        return sprintf("微信订单【%s】状态:%s", $orderNo, $result['data']['status']);
    }
}

// 测试代码
try {
    // 1. 支付宝支付适配
    $alipay = new Alipay();
    $alipayAdapter = new AlipayAdapter($alipay);
    echo "支付宝支付:" . $alipayAdapter->pay(199.9, "OD20260106001") . "<br/>";
    echo "支付宝状态查询:" . $alipayAdapter->queryStatus("OD20260106001") . "<br/><br/>";

    // 2. 微信支付适配
    $wechatPay = new WechatPay();
    $wechatPayAdapter = new WechatPayAdapter($wechatPay);
    echo "微信支付:" . $wechatPayAdapter->pay(299.0, "OD20260106002") . "<br/>";
    echo "微信状态查询:" . $wechatPayAdapter->queryStatus("OD20260106002") . "<br/>";
} catch (\Exception $e) {
    echo "支付处理异常:" . $e->getMessage();
}

核心结构拆解

适配器模式的核心由“目标接口”“原始类”和“适配器类”三部分组成,结构清晰且职责明确:

结构组成 详细说明
目标接口(PaymentAdapterInterface) 定义客户端期望的统一接口规范(如 pay queryStatus),是客户端与适配器的交互标准
原始类(Alipay、WechatPay 等) 接口不兼容的现有类(如第三方库、老系统组件),封装了核心功能,但无法直接被客户端调用
适配器类(AlipayAdapter、WechatPayAdapter 等) 实现目标接口,持有原始类的引用(对象适配器),通过转换参数格式、方法名映射等方式,将客户端的调用转发给原始类,同时适配返回结果格式

关键优化点

优化方向 详细说明
采用对象组合方式 适配器通过构造函数注入原始类实例(组合而非继承),支持适配多个原始类(如一个适配器同时适配支付和退款接口),且降低与原始类的耦合
接口严格规范化 目标接口明确定义方法签名(参数类型、返回值类型),确保所有适配器的实现一致性,客户端可无缝切换不同适配器
屏蔽原始类细节 适配器封装了原始类的接口差异(如参数顺序、方法名、返回格式),客户端无需关注原始类的具体实现,仅需调用统一接口
支持灵活扩展 新增支付方式(如银联支付)时,仅需新增对应的适配器类实现目标接口,无需修改客户端和已有适配器,符合“开放-封闭原则”

常见错误规避

错误类型 问题描述及解决方案
适配器继承原始类(类适配器) 问题:类适配器通过继承原始类实现适配,无法适配多个原始类,且继承会增强耦合,原始类变更可能导致适配器失效。
解决:改用对象适配器(组合原始类),通过构造函数注入原始类实例,提升灵活性
适配器新增业务逻辑 问题:在适配器中添加支付校验、日志记录等业务逻辑,导致适配器职责过重,与“仅做接口转换”的核心目标背离。
解决:适配器仅负责接口格式转换,业务逻辑应封装在客户端或专门的业务类中
目标接口与原始类功能不匹配 问题:目标接口定义的功能超出原始类能力范围(如目标接口需“退款”功能但原始类不支持),导致适配器无法实现完整接口。
解决:设计目标接口时需基于原始类的实际能力,或拆分接口为多个细分接口,避免功能不匹配
客户端直接依赖适配器实现 问题:客户端代码直接引用具体适配器类(如 new AlipayAdapter()),导致切换适配器时需修改客户端代码。
解决:客户端应依赖目标接口而非具体适配器,可通过工厂模式创建适配器实例,降低耦合

扩展场景示例(新增适配器无需修改原有代码)

若业务新增“银联支付”功能,其接口与现有目标接口不兼容,仅需新增适配器类,无需修改客户端和已有代码:

// 新增原始类:银联支付(接口不兼容目标接口)
class UnionPay
{
    public function unionPaySubmit(string $orderCode, float $payAmount): string
    {
        return sprintf("银联支付表单生成成功:<form action='https://pay.unionpay.com' method='post'><input name='order' value='%s'><input name='amount' value='%.2f'></form>", $orderCode, $payAmount);
    }

    public function unionPayQuery(string $orderCode): string
    {
        $statusList = ['未支付', '支付成功', '支付失败'];
        return sprintf("银联订单【%s】状态:%s", $orderCode, $statusList[array_rand($statusList)]);
    }
}

// 新增适配器:银联支付适配器
class UnionPayAdapter implements PaymentAdapterInterface
{
    private UnionPay $unionPay;

    public function __construct(UnionPay $unionPay)
    {
        $this->unionPay = $unionPay;
    }

    public function pay(float $amount, string $orderNo): string
    {
        return $this->unionPay->unionPaySubmit($orderNo, $amount);
    }

    public function queryStatus(string $orderNo): string
    {
        return $this->unionPay->unionPayQuery($orderNo);
    }
}

// 客户端调用新增适配器(无需修改原有代码)
$unionPay = new UnionPay();
$unionPayAdapter = new UnionPayAdapter($unionPay);
echo "银联支付:" . $unionPayAdapter->pay(399.5, "OD20260106003") . "<br/>";
echo "银联状态查询:" . $unionPayAdapter->queryStatus("OD20260106003") . "<br/>";

实际应用场景

场景分类 具体说明
第三方API集成 如集成不同短信服务商(阿里云短信、腾讯云短信)、物流查询接口(顺丰、圆通),通过适配器统一接口格式,客户端无需关注不同服务商的API差异
数据库访问适配 项目需兼容多种数据库(MySQL、PostgreSQL、MongoDB),每种数据库的连接、查询接口不同,适配器可将数据库操作统一为标准CRUD接口
老系统接口适配 遗留系统采用SOAP接口,新系统使用RESTful接口,通过适配器将SOAP接口转换为RESTful格式,实现新老系统数据互通
设备接口适配 硬件设备(打印机、传感器)的通信接口不同(串口、TCP/IP、HTTP),适配器可统一设备调用接口,屏蔽底层通信差异

适配器模式与其他模式对比

对比维度 适配器模式 策略模式 装饰器模式
核心目标 转换接口,解决兼容性问题 封装可替换算法,动态切换逻辑 动态添加扩展功能,组合多个职责
对象关系 适配器组合原始类(适配关系) 上下文依赖策略接口(关联关系) 装饰器嵌套包装(包装关系)
执行逻辑 适配器转发调用,仅转换格式 选择一个策略单独执行,替换逻辑 多层装饰器依次执行,叠加功能
适用场景 接口不兼容、第三方组件集成 算法动态切换、多规则选择 功能动态扩展、多职责组合