[TOC] #### 1. 适配器模式 --- 适配器模式(Adapter Pattern)是一种结构型设计模式,核心作用:解决已有类与目标接口不兼容的问题 + 它充当两个不兼容接口之间的桥梁,将一个类的接口转换成客户端期望的另一个接口 在 PHP 开发中,适配器模式非常实用,常见于以下场景(适配器模式的适用场景): + 集成第三方库:第三方库提供的接口与当前项目规范不匹配 + 对接遗留系统:老系统的代码接口陈旧,但新功能需要以统一的现代接口去调用它 + 统一内部接口:项目中多种功能相似的类(缓存系统/支付网关),通过适配器可以将它们的接口统一化,方便随时切换 适配器模式的三个核心角色: + 目标接口:当前项目中所期望的标准接口 + 适配者(Adaptee):需要被适配的现有类,它的接口与目标接口不兼容 + 适配器(Adapter):实现目标接口,并在内部持有适配者的实例,负责将目标接口的方法调用 “翻译” 并转发给适配者 适配器模式的两种实现方式:对象适配器、类适配器(使用继承) #### 2. 类适配器(使用继承) --- ```php // 目标接口:支付接口 interface PaymentTarget { public function pay($amount); public function refund($transactionId); } // 被适配者:第三方支付宝支付类(接口不兼容) class AlipayAdaptee { public function alipayTransfer($amount, $account) { return "支付宝支付:{$amount}元 到账户{$account}"; } public function alipayRefund($orderId) { return "支付宝退款:订单{$orderId}"; } } // 类适配器:使用继承 class AlipayClassAdapter extends AlipayAdaptee implements PaymentTarget { private $account = 'default@alipay.com'; public function pay($amount) { // 调用父类方法进行适配 return $this->alipayTransfer($amount, $this->account); } public function refund($transactionId) { return $this->alipayRefund($transactionId); } } // 客户端使用 class PaymentClient { private $payment; public function __construct(PaymentTarget $payment) { $this->payment = $payment; } public function processPayment($amount) { echo $this->payment->pay($amount); } } // 使用示例 $adapter = new AlipayClassAdapter(); $client = new PaymentClient($adapter); $client->processPayment(100); // 输出:支付宝支付:100元 到账户default@alipay.com ``` #### 3. 对象适配器(使用组合) --- 假设你的项目规定所有邮件类都必须实现 `IMailer` 接口,该接口拥有 `send()` 方法(实现接口的类必须实现该方法) 但现在需要引入一个第三方邮件库 `PHPMailer`,它的方法却是 `sendMessage()`,可以使用适配器来解决这个问题 ```php // 1. 项目期望的标准接口 interface IMailer { public function send(string $to, string $subject, string $message); } // 2. 适配者 (Adaptee):不兼容的第三方类 class PHPMailer { public function sendMessage(string $recipient, string $title, string $body) { echo "通过 PHPMailer 发送邮件给: $recipient, 主题: $title, 内容: $body\n"; } } // 3. 适配器 (Adapter):实现目标接口,并包装适配者 class PHPMailerAdapter implements IMailer { private PHPMailer $phpMailer; // 通过构造函数注入适配者实例 public function __construct(PHPMailer $phpMailer) { $this->phpMailer = $phpMailer; } // 实现目标接口的方法,并在内部“翻译”调用适配者的方法 public function send(string $to, string $subject, string $message) { // 将项目标准的参数,转换并调用第三方库的方法 $this->phpMailer->sendMessage($to, $subject, $message); } } // 4. 客户端代码 (Client):只认识目标接口,不需要关心具体是哪个邮件库 class NotificationService { private IMailer $mailer; public function __construct(IMailer $mailer) { $this->mailer = $mailer; } public function notifyUser(string $email) { $this->mailer->send($email, '欢迎注册', '感谢您的注册!'); } } // --- 实际使用 --- $legacyMailer = new PHPMailer(); $adapter = new PHPMailerAdapter($legacyMailer); // 用适配器包装 $notification = new NotificationService($adapter); $notification->notifyUser('user@example.com'); ``` #### 4. 实际应用场景 --- ##### 场景1:第三方API适配 ```php // 场景:整合多个物流公司的API接口 // 目标接口:统一物流查询接口 interface LogisticsTarget { public function track($trackingNumber); public function calculateFee($weight, $destination); } // 被适配者:顺丰物流API class SFExpressAdaptee { public function sfQueryOrder($orderId) { return [ 'status' => 'delivered', 'location' => 'Beijing', 'time' => date('Y-m-d H:i:s') ]; } public function sfGetPrice($weight, $cityCode) { return $weight * 12; // 每公斤12元 } } // 被适配者:圆通物流API class YTExpressAdaptee { public function ytGetTrackingInfo($waybillNo) { return [ 'state' => 'in_transit', 'current_city' => 'Shanghai', 'update_time' => time() ]; } public function ytCalcCost($kg, $targetCity) { return $kg * 8; } } // 顺丰适配器 class SFAdapter implements LogisticsTarget { private $sf; public function __construct(SFExpressAdaptee $sf) { $this->sf = $sf; } public function track($trackingNumber) { $result = $this->sf->sfQueryOrder($trackingNumber); // 数据格式转换 return [ 'status' => $this->convertStatus($result['status']), 'location' => $result['location'], 'timestamp' => $result['time'] ]; } public function calculateFee($weight, $destination) { // 城市名称转城市代码(简化处理) $cityCode = $this->getCityCode($destination); return $this->sf->sfGetPrice($weight, $cityCode); } private function convertStatus($sfStatus) { $map = ['delivered' => '已签收', 'pending' => '配送中']; return $map[$sfStatus] ?? '未知'; } private function getCityCode($cityName) { // 实际应调用API或查表 return $cityName == '北京' ? '010' : '000'; } } // 圆通适配器 class YTAdapter implements LogisticsTarget { private $yt; public function __construct(YTExpressAdaptee $yt) { $this->yt = $yt; } public function track($trackingNumber) { $result = $this->yt->ytGetTrackingInfo($trackingNumber); return [ 'status' => $result['state'] == 'delivered' ? '已签收' : '运输中', 'location' => $result['current_city'], 'timestamp' => date('Y-m-d H:i:s', $result['update_time']) ]; } public function calculateFee($weight, $destination) { return $this->yt->ytCalcCost($weight, $destination); } } // 物流管理系统 class LogisticsManager { private $adapters = []; public function addLogistics($company, LogisticsTarget $adapter) { $this->adapters[$company] = $adapter; } public function trackPackage($company, $trackingNumber) { if (!isset($this->adapters[$company])) { throw new Exception("不支持的物流公司:{$company}"); } return $this->adapters[$company]->track($trackingNumber); } } // 使用示例 $manager = new LogisticsManager(); $manager->addLogistics('sf', new SFAdapter(new SFExpressAdaptee())); $manager->addLogistics('yt', new YTAdapter(new YTExpressAdaptee())); // 统一调用方式 $result = $manager->trackPackage('sf', 'SF123456789'); print_r($result); ``` ##### 场景2:遗留系统接口适配 ```php // 场景:新系统需要调用老系统的用户验证功能 // 新系统的目标接口 interface AuthTarget { public function authenticate($username, $password); public function getUserRoles($userId); public function getUserPermissions($userId); } // 老系统(遗留代码)- 假设无法修改 class LegacyAuthSystem { // 老系统的验证方法:用户名密码拼接后MD5 public function legacyLogin($user, $pass) { $encryptedPass = md5($user . $pass . 'salt'); // 查询数据库逻辑... return $userId ?? false; } // 老系统的权限获取 public function legacyGetPermissions($uid) { // 返回逗号分隔的权限字符串 return 'read,write,delete'; } } // 适配器 class LegacyAdapter implements AuthTarget { private $legacy; public function __construct(LegacyAuthSystem $legacy) { $this->legacy = $legacy; } public function authenticate($username, $password) { $userId = $this->legacy->legacyLogin($username, $password); return $userId !== false; } public function getUserRoles($userId) { // 从权限字符串提取角色信息 $permissions = $this->legacy->legacyGetPermissions($userId); return $this->extractRolesFromPermissions($permissions); } public function getUserPermissions($userId) { $permissions = $this->legacy->legacyGetPermissions($userId); return explode(',', $permissions); } private function extractRolesFromPermissions($permissions) { $roles = []; if (strpos($permissions, 'delete') !== false) { $roles[] = 'admin'; } elseif (strpos($permissions, 'write') !== false) { $roles[] = 'editor'; } else { $roles[] = 'viewer'; } return $roles; } } // 新系统的用户服务 class UserService { private $auth; public function __construct(AuthTarget $auth) { $this->auth = $auth; } public function login($username, $password) { if ($this->auth->authenticate($username, $password)) { return "登录成功"; } return "登录失败"; } } // 使用 $legacy = new LegacyAuthSystem(); $adapter = new LegacyAdapter($legacy); $userService = new UserService($adapter); echo $userService->login('admin', '123456'); ``` #### 5. 适配器模式的优缺点 --- 优点: + 提高复用性:让不兼容的类可以协同工作 + 灵活性高:可以随时添加新的适配器 + 符合开闭原则:不修改原有代码,只增加适配器 + 降低耦合:客户端与具体类解耦 缺点: + 增加复杂度:需要额外引入适配器类 + 可能降低性能:额外的适配层会带来性能开销 + 过多适配器:系统设计混乱,难以维护 总结:在系统设计初期应该尽量统一接口规范,适配器主要用于解决 “既成事实” 的不兼容问题 #### 6. 最佳实践建议 --- + 优先使用对象适配器:PHP 单继承限制,对象适配器更灵活 + 不要过度使用:设计阶段应统一接口,适配器用于整合遗留代码 + 文档清晰:明确标注适配器的转换逻辑 + 性能考虑:适配器被频繁调用时注意性能影响 + 结合工厂模式:使用工厂创建适配器实例 ```php // 工厂模式 + 适配器模式 class AdapterFactory { public static function create($type) { switch ($type) { case 'alipay': return new AlipayObjectAdapter(new AlipayAdaptee()); case 'wechat': return new WechatObjectAdapter(new WechatAdaptee()); default: throw new Exception("不支持的支付类型"); } } } // 客户端简洁调用 $payment = AdapterFactory::create('alipay'); $payment->pay(100); ```