Skip to content

适配器模式(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 Adaptee

2.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 data

2.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}"),
        };
    }
}

七、全文总结

适配器模式的核心是 将不兼容的接口转换为目标接口,实现旧代码复用

核心要点

  1. 对象适配器(组合)优于类适配器(继承)
  2. 适配器是连接不兼容接口的桥梁
  3. 适用于整合第三方库、新旧系统迁移
  4. 避免多层嵌套适配器,保持单一职责
  5. 可与工厂模式结合,统一创建适配器实例