December 19, 2025 (4mo ago) — last updated February 10, 2026 (2mo ago)

适配器模式:TypeScript、React & Node.js 示例

了解适配器模式如何通过 TypeScript、React 和 Node.js 示例将不兼容的接口连接起来,现代化集成与遗留代码。

← Back to blog
Cover Image for 适配器模式:TypeScript、React & Node.js 示例

了解适配器模式如何通过 TypeScript、React 和 Node.js 示例将不兼容的接口连接起来,现代化集成与遗留代码。

适配器模式:TypeScript、React & Node.js 示例

摘要: 学习适配器模式如何在 TypeScript、React 和 Node.js 中连接不兼容的接口,并通过实用的真实示例说明其用法。

介绍

是否遇到过一个很好用的库或遗留模块却无法与系统的其余部分契合?这就像试图把欧洲插头插入北美插座 —— 两者都能工作,但接口不匹配。适配器模式通过将一种接口转换为另一种接口来解决这个问题,从而让你在不修改现有代码的情况下重用它们。

本指南解释了该模式,展示了 TypeScript 和 React 的示例,并演示了一个将基于回调的模块现代化的 Node.js 适配器。它侧重于你可以立即应用的实用技术,以清理集成并提高可测试性。

为什么适配器模式很重要

适配器模式是一种结构型模式,它包装一个不兼容的对象并暴露出你的代码所期望的接口。该模式最早由“四人帮”(Gang of Four)在 1994 年记录1。适配器对于集成第三方 API、现代化遗留代码和统一不同的数据源至关重要。

适配器常见的使用场景:

  • 集成返回不同数据结构的第三方 API。
  • 将遗留的回调式 API 现代化为可与 async/await 一起使用的接口。
  • 将多个数据源统一为用于 UI 组件的单一接口。

使用适配器可以让业务逻辑保持清晰,并与外部系统解耦。

适配器模式概览

概念描述
类型结构型
主要意图允许接口不兼容的对象协同工作
核心思想包装被适配者(adaptee),以暴露目标接口(target)
解决的关键问题在不更改源代码的情况下重用现有类
常见用例第三方库、遗留代码、多个数据源

结构与角色

该模式有四个角色:

  1. 客户(Client)—— 需要特定接口的代码。
  2. 目标接口(Target Interface)—— 客户期望的契约。
  3. 被适配者(Adaptee)—— 拥有所需功能但接口不兼容的类或模块。
  4. 适配器(Adapter)—— 实现目标接口并委托给被适配者,根据需要翻译调用。

两种常见的适配器风格:

  • 对象适配器(组合):适配器持有被适配者的实例。这是最灵活的方法。
  • 类适配器(继承):适配器继承自被适配者并实现目标接口。这需要多重继承,在现代 JavaScript 和 TypeScript 中较少使用。

实用示例:TypeScript + React

想象一个仪表盘从两个服务接收用户配置文件,但响应结构不同。如果没有适配器,组件会充斥大量条件逻辑。

不兼容的 API 结构

// Data from UserServiceA
interface UserA {
  userId: number;
  fullName: string;
  emailAddress: string;
}

// Data from UserServiceB
interface UserB {
  id: string;
  name: string;
  contact: {
    email: string;
  };
}

我们应用期望的目标接口

interface UnifiedUser {
  id: string;
  name: string;
  email: string;
}

TypeScript 适配器

// Adapter for UserServiceA
function adaptUserA(userA: UserA): UnifiedUser {
  return {
    id: userA.userId.toString(),
    name: userA.fullName,
    email: userA.emailAddress,
  };
}

// Adapter for UserServiceB
function adaptUserB(userB: UserB): UnifiedUser {
  return {
    id: userB.id,
    name: userB.name,
    email: userB.contact.email,
  };
}

将转换逻辑集中化可以让组件保持简洁且更具弹性。如果某个 API 重命名了字段,只需修改适配器。

使用统一数据的 React 组件

interface UserProfileProps {
  user: UnifiedUser;
}

const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>ID: {user.id}</p>
      <p>Email: {user.email}</p>
    </div>
  );
};

该组件依赖于单一且可预测的数据结构,从而使测试和重用变得简单明了。

示例:将基于回调的 Node.js 模块现代化

遗留模块通常使用错误优先(error-first)的回调。与其修改一个稳定的模块,不如构建一个暴露 Promise 接口的适配器。

遗留的被适配者(请勿修改)

// legacyFileProcessor.js
const fs = require('fs');

class LegacyFileProcessor {
  processFile(filePath, callback) {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        return callback(err, null);
      }
      const processedContent = data.toUpperCase();
      callback(null, processedContent);
    });
  }
}

module.exports = LegacyFileProcessor;

返回 Promise 的适配器

// FileProcessorAdapter.js
const LegacyFileProcessor = require('./legacyFileProcessor');

class FileProcessorAdapter {
  constructor() {
    this.legacyProcessor = new LegacyFileProcessor();
  }

  processFile(filePath) {
    return new Promise((resolve, reject) => {
      this.legacyProcessor.processFile(filePath, (err, data) => {
        if (err) return reject(err);
        resolve(data);
      });
    });
  }
}

module.exports = FileProcessorAdapter;

这与 Node 的 util.promisify 行为类似,但保持了显式且可测试的适配逻辑3

在应用代码中使用适配器

const FileProcessorAdapter = require('./FileProcessorAdapter');
const fileProcessor = new FileProcessorAdapter();

async function handleFileProcessing() {
  try {
    console.log('Processing file with modern async/await...');
    const content = await fileProcessor.processFile('my-file.txt');
    console.log('Processed Content:', content);
  } catch (error) {
    console.error('An error occurred:', error);
  }
}

handleFileProcessing();

这能在不修改遗留代码的情况下,为代码库的其余部分提供现代化接口。

何时使用适配器

当两个组件因接口不同而无法直接通信时,使用适配器。典型场景:

  • 集成输入或输出与你的模型不匹配的第三方 API。
  • 包装遗留的回调 API,使其可与 async/await 一起使用。
  • 为多个格式不同的数据源支持,针对每个来源创建一个适配器。

何时不应使用适配器:

  • 如果你控制两个系统且小范围重构即可解决不匹配问题,应优先直接重构。
  • 如果你的目标是简化复杂子系统,考虑使用外观(Facade)模式。外观提供简化的高层入口;适配器则专注于兼容性。

快速决策检查表

情况使用适配器?原因
需要使用接口不兼容的第三方库你无法更改该库,因此必须适配它
控制双方且改动很小直接重构以避免额外的间接层
需要对复杂系统提供简化的高层接口外观模式更合适
逐步迁移遗留系统将旧组件包装以匹配新接口
多个结构不同的数据源适配器将它们统一为一种结构

测试与性能

适配器通过将核心逻辑与外部系统解耦来提高可测试性。你可以模拟适配器的接口以隔离测试组件,并单独测试适配器以验证转换逻辑。

适配器带来的性能开销很小 —— 通常只是多一次函数调用 —— 与网络 I/O 或数据库查询相比可以忽略不计。对于大多数 Web 应用来说,维护性和解耦带来的好处远超这微小的成本。根据 Stack Overflow 开发者调查,JavaScript 仍然是使用最广泛的语言,说明开发者经常需要处理适配器可以解决的集成工作4

常见问题解答

问:适配器模式解决了什么问题?

答:它通过将客户端的调用翻译为被适配者能理解的调用来解决接口不兼容问题,从而让你在不修改代码的情况下重用已有代码。

问:适配器如何帮助处理遗留代码?

答:适配器包装遗留模块并暴露现代接口,使你能够在不进行高风险重写的前提下将旧且稳定的代码集成到新应用中。

问:什么时候应选择适配器而不是其他模式?

答:当你需要在两个不匹配的接口之间实现兼容性时,选择适配器。如果你想简化整个子系统,应该使用外观(Facade)模式。

延伸阅读与内部链接


在 Clean Code Guy,我们帮助团队实现实用的设计模式,将脆弱、复杂的代码库转变为具有弹性、可测试且令人愉快的资产。如果你正在与遗留系统或棘手的集成作斗争,我们的 Clean Code 审计可以为你提供清晰、可行的路线图,帮助构建更健康的架构。 了解我们如何帮助你更快交付更好的代码

1.
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). [https://en.wikipedia.org/wiki/Design_Patterns_(book)](https://en.wikipedia.org/wiki/Design_Patterns_(book))
2.
Adapter pattern overview and examples: https://www.geeksforgeeks.org/adapter-pattern/
3.
Node.js documentation for util.promisify, a common approach to convert callbacks to Promises: https://nodejs.org/api/util.html#utilpromisifyoriginal
4.
Stack Overflow Developer Survey 2023, showing the prevalence of JavaScript and web technologies that commonly require integration work: https://survey.stackoverflow.co/2023/
← Back to blog
🙋🏻‍♂️

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

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