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


