January 7, 2026 (3mo ago)

整洁代码中的适配器设计模式指南

通过真实的 TypeScript 示例深入探索适配器设计模式。学习如何桥接不兼容的 API、重构遗留代码并构建可扩展的系统。

← Back to blog
Cover Image for 整洁代码中的适配器设计模式指南

通过真实的 TypeScript 示例深入探索适配器设计模式。学习如何桥接不兼容的 API、重构遗留代码并构建可扩展的系统。

The Adapter 设计模式本质上是一种中介。把它想象成一个翻译器,让两个不兼容的系统毫无障碍地互相通信。其核心目的在于让现有类协同工作,而无需触碰它们的原始源码。它相当于软件领域的旅行转换插头,让你把加拿大的电子设备插入欧洲的插座。

这个模式是编写整洁且可维护代码的基石,特别是在你需要集成第三方库或处理遗留系统时。1

为什么你的代码库需要适配器模式

一个图示展示了旧的 XML API 通过适配器连接到现代 JSON 应用。

想象一下:你在欧洲,试图把加拿大笔记本电脑插入墙上的插座。就是插不进去。你的充电器没问题,插座也没问题,但它们的接口完全不同。软件开发中也经常发生完全相同的问题——当我们需要将两套从未为相互协作而设计的系统强行配合时。

这正是适配器模式发挥作用的地方。它就是代码的通用旅行适配器,在不匹配的组件之间创建无缝桥梁。

在现代开发中弥合差距

如今我们很少从零开始构建一切。我们从第三方库、外部 API 与遗留系统中组装解决方案。这加快了交付速度,但也带来了不兼容的接口和诱人的捷径,从而产生长期痛点:

  • 代码重复:相同的转换逻辑散落在应用各处。
  • 高耦合:业务逻辑与外部服务细节纠缠在一起。
  • 技术债务上升:每次 API 变更都会引发对脆弱集成代码的漫长追踪。

适配器模式为你提供了一个专用的适配器类,在一个地方处理翻译,从而保护核心应用逻辑并使集成更易维护。这种做法在那些优先考虑模块化架构和逐步现代化的团队中很常见。2

适配器模式不仅仅是让事情能工作的临时手段。它通过将核心应用逻辑与外部系统的混乱细节隔离开来,保护了应用的简洁性与完整性。

适配器有助于编写整洁且可扩展的代码

适配器是一种构建灵活架构的策略,使得系统能够在不完全重写的情况下演化。你可以通过添加或替换适配器来交换服务或逐步淘汰遗留系统,而无需修改客户端代码。这种稳定性能加速功能开发并降低集成风险。2

适配器模式到底如何工作

其核心思想是在系统的各个部分之间创建清晰的分离,以便它们能独立演化。四个角色让这个模式易于理解:

  1. 客户端(Client):需要完成某项工作并期望一个简洁、稳定接口的代码。
  2. 目标(Target):客户端使用的清晰接口。
  3. 被适配者(Adaptee):具有不兼容接口的现有组件(通常不可更改)。
  4. 适配器(Adapter):实现 Target 并将调用翻译给 Adaptee。

一张手绘图示说明了适配器设计模式,展示了 Client、Target 接口、Adapter 和 Adaptee。

客户端只与 Target 接口通信;适配器在背后为 Adaptee 进行悄悄的翻译。这种设计与开闭原则(Open/Closed Principle)高度契合:你可以通过编写新适配器来集成新服务,同时保持现有客户端代码不变。1

适配器模式的主要目标是将一个类的接口转换为客户端期望的另一个接口。适配器使得原本因接口不兼容而无法协同工作的类能够一起工作。3

使用真实示例在 TypeScript 中构建适配器

图示说明了适配器设计模式,将 XML API 数据转换为供前端使用的 JSON(使用 TypeScript)。

下面给出两个实用的 TypeScript 示例,反映了常见的现实问题:适配遗留的 XML API 与统一第三方支付网关。

示例 1:将遗留的 XML API 适配为 JSON

场景:你的现代 React 前端期望接收 JSON,但唯一的数据源是返回 XML 的遗留服务。客户端应使用一个干净的 IUserService 接口;LegacyUserService 返回的是 XML。适配器将在两者之间架桥。

不兼容的被适配者

// Adaptee: The old service with an incompatible interface
class LegacyUserService {
  fetchUsersXML(): string {
    return `
      <users>
        <user id="1">
          <name>Alice</name>
          <email>alice@example.com</email>
        </user>
        <user id="2">
          <name>Bob</name>
          <email>bob@example.com</email>
        </user>
      </users>
    `;
  }
}

目标接口与适配器

interface IUser {
  id: number;
  name: string;
  email: string;
}

interface IUserService {
  getUsers(): Promise<IUser[]>;
}

class UserServiceAdapter implements IUserService {
  private adaptee: LegacyUserService;

  constructor(legacyService: LegacyUserService) {
    this.adaptee = legacyService;
  }

  async getUsers(): Promise<IUser[]> {
    const xmlData = this.adaptee.fetchUsersXML();
    // Use a robust XML parser in production (e.g., xml2js).
    console.log("Translating XML to JSON...");
    return [
      { id: 1, name: "Alice", email: "alice@example.com" },
      { id: 2, name: "Bob", email: "bob@example.com" },
    ];
  }
}

有了这个适配器,客户端代码只需与 IUserService 交互,而无感知底层是 XML。

示例 2:统一第三方支付网关接口

场景:你的应用使用一个标准的 IPaymentProcessor 接口,但 PayWizard 网关的方法是 startTransaction 和 verifyPaymentStatus。适配器将你的标准调用映射到 PayWizard 的 API。

不一致的被适配者

class PayWizard {
  startTransaction(amount: number, cardDetails: string): string {
    console.log(`PayWizard: Initiating transaction for $${amount}.`);
    const transactionId = "pw_" + Math.random().toString(36).substr(2, 9);
    return transactionId;
  }

  verifyPaymentStatus(transactionId: string): boolean {
    console.log(`PayWizard: Verifying status for ${transactionId}.`);
    return true;
  }
}

目标接口与适配器

interface IPaymentProcessor {
  processPayment(amount: number, cardInfo: string): Promise<string>;
  checkStatus(id: string): Promise<boolean>;
}

class PayWizardAdapter implements IPaymentProcessor {
  private payWizard: PayWizard;

  constructor() {
    this.payWizard = new PayWizard();
  }

  async processPayment(amount: number, cardInfo: string): Promise<string> {
    console.log("Adapter: Translating 'processPayment' to 'startTransaction'.");
    return this.payWizard.startTransaction(amount, cardInfo);
  }

  async checkStatus(id: string): Promise<boolean> {
    console.log("Adapter: Translating 'checkStatus' to 'verifyPaymentStatus'.");
    return this.payWizard.verifyPaymentStatus(id);
  }
}

使用适配器可以让你的应用代码在不同提供者之间保持整洁且一致。

使用适配器重构遗留代码

图示说明了使用适配器模式连接服务器到客户端的四步系统迁移过程。

每个项目最终都会面临遗留代码的问题。适配器模式让你通过用现代 Target 接口封装旧系统并逐步迁移客户端代码,从而避免高风险的大规模重写。这种方法降低了风险并支持可控的现代化计划。2

逐步迁移计划

  1. 定义理想的 Target 接口。
  2. 创建实现该接口并接受遗留 Adaptee 的 Adapter 类。
  3. 在适配器内部实现转换逻辑。
  4. 逐步迁移客户端代码以使用适配器。

使用 AI 加速重构

现代 AI 工具可以加速样板代码的生成,让你把精力放在关键的映射逻辑上,但架构决策仍然是团队的责任。使用工具来脚手架适配器,然后编写测试来验证映射。

使用适配器不仅仅是临时补丁;它是对架构健康的战略性投资,能支持逐步现代化。2

在适配器与其他模式之间做选择

选择正确的模式很重要。Adapter、Decorator、Proxy 和 Façade 可能看起来相似,但它们目标不同。使用 Adapter 来改变接口;使用 Decorator 来添加行为;使用 Proxy 来控制访问;使用 Façade 来简化复杂子系统。

适配器 vs 装饰器

适配器转换接口。装饰器在保留原始接口的同时添加职责。

适配器 vs 代理

代理保持相同接口并控制访问或添加延迟初始化、缓存或日志。适配器改变接口,使客户端能够使用本来不兼容的组件。

适配器 vs 外观(Façade)

外观在单一接口后简化子系统。适配器专注于转换单个对象的接口,以便两个组件可以互操作。

PatternPrimary IntentWhen to Use
Adapter将一种接口转换为另一种当现有类必须与不兼容的客户端协作时。
Decorator添加职责当你想动态扩展行为时。
Proxy控制访问当你需要延迟加载、访问控制或日志时。
Façade简化子系统当你想为复杂行为提供单一入口点时。

将适配器模式带到你的团队中

为了避免滥用,为适配器创建设定清晰的护栏:

  • 文档:每个适配器需要一个 README,说明 Adaptee、Target 接口和映射关系。
  • 测试:要求有断言转换逻辑的单元测试。
  • 性能监控:当适配器处在热点路径时对关键适配器进行基准测试。

在 CI 中添加自动检查以执行这些规则,保持适配器在整个代码库中的一致性。在内部文档站点上提供示例和模板,或链接到像 modernizing legacy systems 这样的指南。

有问题?让我们聊聊适配器

问答

问:什么时候适配器比完全重写更合适?

答:当现有组件运行良好但其接口不符合你的需要时,尤其是对于稳定的遗留系统或你无法控制的第三方 API,使用适配器是合适的。如果组件存在缺陷或缺少必要功能,则可能需要重写。

问:适配器会带来明显的性能开销吗?

答:适配器会增加很小的开销——一次额外的方法调用或一个转换步骤——但在大多数业务应用中,这与网络或 I/O 成本相比可以忽略不计。对于对延迟敏感的系统,请对适配器进行基准测试。

问:我的团队应如何测试适配器?

答:编写关注 Target 与 Adaptee 之间映射的单元测试。在适当情况下对 Adaptee 进行模拟,并包含集成测试以确保适配器在与真实依赖交互时行为正确。

1.
Refactoring Guru,“Adapter,” https://refactoring.guru/design-patterns/adapter
2.
Clean Code Guy,“Modernizing Legacy Systems,” https://cleancodeguy.com/blog/modernizing-legacy-systems
3.
Wikipedia,“Adapter pattern,” https://en.wikipedia.org/wiki/Adapter_pattern
← Back to blog
🙋🏻‍♂️

AI编写代码。
您让它持久。

在AI加速的时代,干净代码不仅仅是好的实践 — 它是能够扩展的系统与在自己的重量下崩溃的代码库之间的区别。

整洁代码中的适配器设计模式指南 | Clean Code Guy