PHP设计模式之适配器模式(Adapter)原理与用法详解
前言适配器模式Adapter Pattern解决的是一个非常具体的问题你需要的接口和手上已有的类的接口对不上。它通过增加一个中间类把一个类的接口「翻译」成客户端期望的另一种接口。生活中最贴切的类比是电源转接头笔记本要的是某个形状的插头墙上只有另一种插座转接头不改变电只改变「接口形状」。围绕它有三个常见误解把适配器当成装饰器Decorator。装饰器是在保持同一接口的前提下增强行为比如给日志加一层「同时写文件」适配器改变接口形态让不兼容的东西能对接。判断标准很简单客户端代码调用前后的方法签名是否一致——一致是装饰不一致是适配。以为适配器可以随便加。适配器是「隔离层」它存在的意义是把不可控的外部依赖挡在一个角落。如果给自己的每个类都加一层适配器那只是把代码量翻倍并没有隔离任何风险。以为必须继承被适配的类。这类「类适配器」在 PHP 里存在明显短板下面有对比表实际项目里用的是对象适配器——持有被适配对象的实例把调用委托过去。适配器最常见的真实战场是接入第三方 SDK不同厂商的方法名、参数顺序、返回结构、错误码各不相同而业务代码不应该认识它们。本文用「第三方日志库」和「短信服务商」两个例子讲清对象适配器的写法与边界。一、三个角色角色职责本文示例目标接口Target业务代码期望的接口由你自己的项目定义AppLogger适配者Adaptee已有的、接口不兼容的类通常来自第三方VendorLogger适配器Adapter实现目标接口把调用翻译成对适配者的调用VendorLoggerAdapter关键约束是业务代码只依赖目标接口适配者与适配器都藏在边界之外。这样替换第三方时只需要改适配器甚至只换一个配置项业务代码不动。二、对象适配器实战把第三方日志库接进项目假设项目内部约定了一个日志接口而引入的第三方日志库方法名、级别命名、参数结构都不一样?php // 适用于 PHP 8.0match 需要 8.0declare(strict_types1);// 【目标接口】项目内部统一使用它interface AppLogger{public function log(string $level, string $message, array $context []): void;}// 【适配者】第三方库方法名不同、没有 context 参数、级别用 warn/error 这类词final class VendorLogger{public function writeLine(string $severity, string $text): void{printf([%s] %s\n, strtoupper($severity), $text);}}// 【适配器】实现目标接口把调用翻译后委托给适配者final class VendorLoggerAdapter implements AppLogger{private VendorLogger $vendor;public function __construct(VendorLogger $vendor){$this-vendor $vendor;}public function log(string $level, string $message, array $context []): void{// 1) 翻译级别把项目内的级别词映射到第三方认识的那几个$severity match (strtolower($level)) {warning warn,error, critical, alert, emergency error,default info,};// 2) 翻译参数第三方不支持结构化 context用 JSON 拼进文本if ($context ! []) {$message . . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);}// 3) 委托真正的活由适配者干$this-vendor-writeLine($severity, $message);}}// 业务代码只认识 AppLoggerfunction registerUser(AppLogger $logger, string $name): void{$logger-log(warning, 库存不足, [sku A-100, left 2]);$logger-log(info, 用户注册, [name $name]);}registerUser(new VendorLoggerAdapter(new VendorLogger()), ann);如果项目跑在 PHP 7.x 上把match换成switch即可PHP 7 没有match?php // 适用于 PHP 7.0 的等价写法$severity info;switch (strtolower($level)) {case warning:$severity warn;break;case error:case critical:case alert:case emergency:$severity error;break;}三、对象适配器 vs 类适配器维度类适配器继承适配者对象适配器持有适配者实现方式extends VendorLogger implements AppLogger构造函数注入适配者内部委托PHP 里的可行度低——单继承继承位置被占掉高项目里基本都用这种适配多个适配者做不到可以组合多个对象适配适配者的子类被具体类绑死可以注入不同实现测试友好度差无法替换被继承的行为好可注入替身对象PHP 不支持多重继承因此「类适配器」只在极少数场景下可用。这也是为什么在 PHP 里谈适配器默认指的都是对象适配器。顺带澄清trait是代码复用机制不是「多继承的替代品」用它来实现接口适配只会让方法来源变得难以追踪。四、统一多个厂商适配器最典型的用法同一个能力接入多家供应商时适配器的价值最明显。业务代码只面向统一的接口每家厂商的差异都被适配器吃掉?php // 适用于 PHP 8.0declare(strict_types1);// 【目标接口】统一契约成功返回消息 ID失败抛异常interface SmsSender{public function send(string $phone, string $text): string;}// 【适配者 A】第三方 SDK数组进、数组出用 ok 字段表示成败final class VendorASdk{/** return array{ok: bool, id?: string, err?: int} */public function sendSms(array $params): array{// 真实 SDK 会发 HTTP 请求这里用固定结构示意return [ok true, id A-10086];}}// 【适配器 A】把数组返回值翻译成「字符串或异常」final class VendorASender implements SmsSender{public function __construct(private VendorASdk $sdk){}public function send(string $phone, string $text): string{$result $this-sdk-sendSms([to $phone,body $text,format json,]);if (($result[ok] ?? false) ! true) {// 翻译错误抛出项目自己的异常不把第三方的返回结构泄漏出去throw new \RuntimeException(短信发送失败厂商错误码: . ($result[err] ?? unknown));}return (string)$result[id];}}// 业务代码完全不知道背后是哪家厂商function notify(SmsSender $sender, string $phone): string{return $sender-send($phone, 您的验证码是 123456);}echo notify(new VendorASender(new VendorASdk()), 13800000000), PHP_EOL;新增厂商时只需要再写一个VendorBSender implements SmsSender业务代码一行都不用改——这就是适配器在「供应商替换」场景里最实际的价值。常见坑点❌ 把适配器当装饰器用让适配器实现同一个接口然后在里面加缓存、加重试。✅ 装饰器保持接口不变并增强行为适配器负责转换接口两种意图混在一起会让调用方分不清「谁在做什么」。要加缓存或重试再套一层装饰器。❌ 在适配器里写业务判断比如「金额超过 100 就要人工审核」。✅ 适配器只做「翻译 委托」业务规则属于领域层混进来会让适配器无法复用到别的场景。❌ 目标接口的方法签名里出现第三方的类型public function send(VendorAResult $r): VendorAResponse。✅ 目标接口只使用自己项目的类型字符串、数组、自定义异常一旦泄漏第三方类型隔离层就失效了。❌ 适配器里catch (\Throwable $e) { return null; }把第三方的失败吞掉。✅ 应把第三方异常翻译成项目自己的异常再抛出保留足够定位问题的信息错误码、请求标识但要避免把敏感信息写进日志。❌ 在适配器内部直接new VendorASdk()或者用单例硬编码第三方对象。✅ 通过构造函数注入如上面的__construct(private VendorASdk $sdk)测试时可以注入替身对象。❌ 为了「解耦」给项目自己的每个类都套一层适配器。✅ 适配器应该只出现在不可控的边界上第三方 SDK、老系统接口、外部队列内部类之间用接口直接协作即可。❌ 认为适配器里应该顺手升级第三方库的调用方式。✅ 适配器正是升级的隔离点第三方换代时改动被限制在适配器内部。但升级要连带更新翻译逻辑方法名、错误码、字段名都可能变并补上对应的测试。❌ 用已经废弃或移除的扩展去实现底层能力例如仍在用mcrypt_*做加解密。✅mcrypt_*从 PHP 7.2 起废弃、已不适合新项目可逆加密改用sodium_*或带随机 nonce 的openssl_encrypt()消息认证用hash_hmac()配合hash_equals()。适配器要适配的是接口不是把过时实现换个包装继续用。总结关注点结论解决什么让接口不兼容的两方协作把第三方差异隔离在边界上三个角色目标接口自己定义、适配者第三方、适配器实现目标并委托首选形态对象适配器持有 委托PHP 单继承下类适配器基本不实用与装饰器的区别装饰器保持接口不变并增强适配器改变接口做转换与工厂的关系工厂负责「创建哪个适配器」适配器负责「怎么转换」适配器的边界只出现在不可控的外部依赖处内部类之间不需要适配器的价值来自一句话把「会变的东西」关进一个类里。第三方的 SDK 会升级、错误码会变、供应商会被替换而业务代码只依赖自己定义的接口——这个接口就是那道墙适配器则是墙上唯一的门。用它的时候要守住两条线目标接口里不出现第三方类型适配器里不写业务逻辑。

相关新闻

Python爬虫实战:requests抓取图片站第一页所有图片

Python爬虫实战:requests抓取图片站第一页所有图片

1. 从零拆解一个图片站抓取需求1.1 这个需求到底在做什么拿到“抓取某个图片站图片区第一页所有图片”这个题目,很多人第一反应是打开编辑器就写requests.get,但真正做过一批站点的人会先停下来想三件事:目标页面是静态渲染还是动态加载、图片…

2026/10/9 13:18:02 阅读更多 →
SNOMED CT 关系数据库落库实战:RF2 导入、递归查询与性能优化

SNOMED CT 关系数据库落库实战:RF2 导入、递归查询与性能优化

简介:这份资源面向医疗信息化开发者、数据工程师及SNOMED CT术语集使用者,提供在关系数据库中表示SNOMED CT的完整脚本方案,解决术语版本导入、建库与查询的落地问题。包内共115个文件,以64个SQL脚本为核心,配合11个Py…

2026/10/9 13:18:02 阅读更多 →
DependenciesGui 实战:Windows 10 下 PE 依赖树分析与 DLL 缺失排查

DependenciesGui 实战:Windows 10 下 PE 依赖树分析与 DLL 缺失排查

简介:DependenciesGui-windows10-depends 是一款面向 Windows 10 用户的依赖关系分析工具,适合需要排查程序加载失败、DLL 缺失问题的普通用户,以及关注打包部署的开发者使用。解压后得到 Releasex64 目录,双击 DependenciesGui.e…

2026/10/9 13:18:02 阅读更多 →

最新新闻

人大金仓KCA/KCP认证备考:从零散题库到可复现的实操路径

人大金仓KCA/KCP认证备考:从零散题库到可复现的实操路径

简介:这份人大金仓KCA、KCP题库整理面向备考金仓数据库认证的考生与数据库运维初学者,聚焦KingbaseES v8核心考点,帮助读者在刷题中快速定位知识盲区、巩固原理细节。内容覆盖系统表存储位置、ORDER BY排序限制、后台进程、模板数据库、索引最…

2026/10/9 13:54:47 阅读更多 →
Java反混淆工具flaming-shame:字节码还原与混淆对抗实战

Java反混淆工具flaming-shame:字节码还原与混淆对抗实战

简介:flaming-shame 是一款面向 Java 逆向工程与安全分析方向的轻量级反混淆工具,适合需要阅读、调试被混淆字节码的开发者与安全研究人员。它通过静态分析手段比较 Java 程序的结构图,尝试自动还原被混淆的类名、方法名与变量名,…

2026/10/9 13:54:47 阅读更多 →
Java反编译工具Mac版实战:CFR与Procyon选型及避坑指南

Java反编译工具Mac版实战:CFR与Procyon选型及避坑指南

简介:这是一份面向 macOS 用户的 Java 反编译工具资源,主要解决在苹果系统下查看与还原 class 字节码的需求,适合 Java 开发、逆向分析及源码排查场景使用。压缩包共 8 个文件,整体约 7.55MB,包含 jar 主程序、sh 启动…

2026/10/9 13:54:47 阅读更多 →
Oracle数据库设计开发规范:从建表到PL/SQL的避坑指南

Oracle数据库设计开发规范:从建表到PL/SQL的避坑指南

简介:Oracle 数据库设计开发规范是一份面向数据库设计人员、开发工程师与运维人员的实用文档,用于解决 Oracle 项目在表结构设计、命名约定、权限分配与安全策略上缺乏统一标准的问题,适合初中级开发者对照落地,也可作为团队内部规…

2026/10/9 13:54:46 阅读更多 →
SQL Server 2005安装图解与SP3补丁部署避坑指南

SQL Server 2005安装图解与SP3补丁部署避坑指南

简介:这份资源是面向数据库初学者与运维人员的 SQL Server 2005 安装图解教程,重点解决版本选择、环境准备与补丁升级等上手难题。内容围绕 SQL Server 2005 的五个版本(企业版、标准版、工作组版、开发版、Express 版)展开对比&a…

2026/10/9 13:54:46 阅读更多 →
从零开始:在Dify中接入高德MCP,用TaoToken统一Key打通Agent工具链

从零开始:在Dify中接入高德MCP,用TaoToken统一Key打通Agent工具链

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 13:53:46 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →