适配器模式(Adapter)
前言
适配器模式是一种结构型设计模式,它能使接口不兼容的对象能够相互协作。就像现实中的电源适配器一样,它将一个接口转换成客户端期望的另一个接口。本文将详细讲解适配器模式的核心原理、实现方式及实际应用。
一、核心概念
1.1 定义
将一个类的接口转换成客户端期望的另一个接口。适配器模式让原本接口不兼容的类可以一起工作。
1.2 核心角色
| 角色 | 说明 |
|---|---|
| 目标接口(Target) | 客户端期望的接口 |
| 适配者(Adaptee) | 需要被适配的现有接口 |
| 适配器(Adapter) | 将适配者接口转换为目标接口 |
1.3 与其他模式的关系
| 模式 | 关系 |
|---|---|
| 装饰器 | 不改变接口,增加功能 |
| 适配器 | 改变接口,连接不兼容类 |
| 外观 | 简化接口,提供新接口 |
| 代理 | 控制访问,接口不变 |
二、代码实现
2.1 对象适配器(推荐)
通过组合方式实现适配器,更灵活。
php
<?php
// 目标接口:客户端期望的接口
interface Target
{
public function request(): string;
}
// 适配者:现有接口,与目标接口不兼容
class Adaptee
{
public function specificRequest(): string
{
return "Specific request from Adaptee";
}
}
// 对象适配器:持有适配者引用,转换接口
class Adapter implements Target
{
private Adaptee $adaptee;
public function __construct(Adaptee $adaptee)
{
$this->adaptee = $adaptee;
}
public function request(): string
{
// 将适配者方法转换为目标方法
return "Adapter: " . $this->adaptee->specificRequest();
}
}
// 客户端代码
function clientCode(Target $target): void
{
echo $target->request() . PHP_EOL;
}
// 使用
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
clientCode($adapter);
// Adapter: Specific request from Adaptee2.2 类适配器
通过继承方式实现适配器(PHP 单继承,类适配器仅适用于适配者无需多继承场景)。
php
<?php
// 目标接口
interface Target
{
public function request(): string;
}
// 适配者
class Adaptee
{
public function specificRequest(): string
{
return "Adaptee data";
}
}
// 类适配器:继承适配者并实现目标接口
class ClassAdapter extends Adaptee implements Target
{
public function request(): string
{
return "ClassAdapter: " . $this->specificRequest();
}
}
// 使用
$adapter = new ClassAdapter();
echo $adapter->request() . PHP_EOL;
// ClassAdapter: Adaptee data2.3 实际案例:第三方日志库适配
php
<?php
// 第三方日志库(不可修改)
class ThirdPartyLogger
{
public function logMessage(string $level, string $message, int $timestamp): void
{
echo "[{$level}] {$timestamp}: {$message}" . PHP_EOL;
}
public function logError(Throwable $error): void
{
fwrite(STDERR, "ERROR: " . $error->getMessage() . PHP_EOL);
}
}
// 目标接口:项目期望的日志接口
interface Logger
{
public function debug(string $message): void;
public function info(string $message): void;
public function warn(string $message): void;
public function error(string $message): void;
}
// 适配器:将第三方日志接口适配为项目接口
class LoggerAdapter implements Logger
{
private ThirdPartyLogger $logger;
public function __construct(ThirdPartyLogger $logger)
{
$this->logger = $logger;
}
public function debug(string $message): void
{
$this->logger->logMessage("DEBUG", $message, time());
}
public function info(string $message): void
{
$this->logger->logMessage("INFO", $message, time());
}
public function warn(string $message): void
{
$this->logger->logMessage("WARN", $message, time());
}
public function error(string $message): void
{
$this->logger->logMessage("ERROR", $message, time());
}
}
// 使用
$thirdPartyLogger = new ThirdPartyLogger();
$logger = new LoggerAdapter($thirdPartyLogger);
$logger->info("Application started");
$logger->error("Something went wrong");2.4 实际案例:新旧 API 适配
php
<?php
// 旧版 API
class OldUserService
{
public function getUserInfo(int $id): array
{
return ['name' => 'Flynn', 'age' => 30];
}
}
// 新版接口
interface NewUserService
{
public function getUser(int $id): array;
}
// 适配器:将旧 API 适配为新接口
class UserServiceAdapter implements NewUserService
{
private OldUserService $oldService;
public function __construct(OldUserService $oldService)
{
$this->oldService = $oldService;
}
public function getUser(int $id): array
{
$info = $this->oldService->getUserInfo($id);
return [
'fullName' => $info['name'],
'userAge' => $info['age'],
];
}
}
// 使用
$oldService = new OldUserService();
$newService = new UserServiceAdapter($oldService);
$user = $newService->getUser(1);
echo $user['fullName'] . PHP_EOL; // Flynn三、适用场景
| 场景 | 说明 |
|---|---|
| 整合第三方库 | 适配库接口与项目接口 |
| 新旧系统迁移 | 旧 API 适配新接口 |
| 复用现有代码 | 接口不兼容时适配 |
| 统一接口 | 多种实现统一为一种接口 |
| 数据格式转换 | 不同数据格式间转换 |
四、优缺点分析
| 优点 | 缺点 |
|---|---|
| 解耦客户端与适配者 | 增加额外类 |
| 复用现有代码 | 可能增加系统复杂度 |
| 符合开闭原则 | 对象适配器需创建适配器实例 |
| 灵活切换适配者 | 类适配器受单继承限制 |
| 透明适配 | 适配过程对客户端透明 |
五、常见踩坑与问题排查
5.1 适配器过度嵌套
php
<?php
// 问题:多层适配器嵌套,难以维护
$adapter1 = new AdapterA(new AdapteeA());
$adapter2 = new AdapterB($adapter1);
$adapter3 = new AdapterC($adapter2);
// 解决:减少适配层级,合并适配逻辑
class UnifiedAdapter implements Target
{
private AdapteeA $adapteeA;
private AdapteeB $adapteeB;
public function request(): string
{
return $this->adapteeA->methodA() . " + " . $this->adapteeB->methodB();
}
}5.2 适配器变成"上帝类"
php
<?php
// 问题:适配器承担过多职责
interface TargetA { public function a(): void; }
interface TargetB { public function b(): void; }
interface TargetC { public function c(): void; }
class GodAdapter implements TargetA, TargetB, TargetC
{
// 过多方法
public function a(): void {}
public function b(): void {}
public function c(): void {}
}
// 解决:单一职责,一个适配器只适配一个接口六、优化方案与进阶
6.1 双向适配器
php
<?php
// 双向适配器:同时适配两个方向
interface Target
{
public function request(): string;
}
interface AdapteeInterface
{
public function specificRequest(): string;
}
class TwoWayAdapter implements Target, AdapteeInterface
{
private ?Target $target;
private ?AdapteeInterface $adaptee;
public function __construct(?Target $target = null, ?AdapteeInterface $adaptee = null)
{
$this->target = $target;
$this->adaptee = $adaptee;
}
// Target 接口方法
public function request(): string
{
return $this->adaptee->specificRequest();
}
// Adaptee 接口方法
public function specificRequest(): string
{
return $this->target->request();
}
}6.2 结合工厂模式
php
<?php
class AdapterFactory
{
public static function createLogger(string $type): Logger
{
return match ($type) {
'console' => new ConsoleLoggerAdapter(),
'file' => new FileLoggerAdapter(),
'thirdparty' => new LoggerAdapter(new ThirdPartyLogger()),
default => throw new RuntimeException("Unknown logger type: {$type}"),
};
}
}七、全文总结
适配器模式的核心是 将不兼容的接口转换为目标接口,实现旧代码复用。
核心要点:
- 对象适配器(组合)优于类适配器(继承)
- 适配器是连接不兼容接口的桥梁
- 适用于整合第三方库、新旧系统迁移
- 避免多层嵌套适配器,保持单一职责
- 可与工厂模式结合,统一创建适配器实例
