Skip to content

适配器模式

1. 核心概念与价值

适配器模式在不改变原有代码的基础上,通过增加一个“中间层”来抹平接口之间的差异。

维度描述
核心意图转换接口。解决“这个东西很好,但我没法直接用”的问题。
主要优点1. 复用性强:让旧的组件或第三方库能在新系统中继续发光发热;
2. 解耦:客户端代码只关注目标接口,不关心底层实现的变化;
3. 灵活性:可以在适配器中加入逻辑,对数据进行预处理。
主要缺点如果系统中充斥着大量的适配器,会增加代码的阅读成本,有时直接重构原始代码反而更清晰。

2. 经典实战场景️

让我们通过两个最常见的开发场景来理解它的实现。

2.1 第三方 SDK 切换(地图组件示例)

假设你的项目原本使用百度地图,现在要切换到高德地图,但它们的渲染方法名完全不同。

角色接口 / 方法名
旧接口 (BaiduMap)render(mapData)
新接口 (GaodeMap)show(data)
目标需求统一使用 render 方法调用。
js
// 1. 旧的组件实现
class BaiduMap {
  render(mapData) { console.log('开始渲染百度地图...', mapData); }
}

// 2. 新的第三方组件(接口不兼容)
class GaodeMap {
  show(data) { console.log('开始渲染高德地图...', data); }
}

// 3. 编写适配器
class GaodeMapAdapter {
  constructor() {
    this.gaodeMap = new GaodeMap();
  }
  // 抹平差异:让 show 变成 render
  render(mapData) {
    this.gaodeMap.show(mapData);
  }
}

// 4. 统一调用
const renderMap = (map) => map.render({ city: 'Beijing' });

renderMap(new BaiduMap());
renderMap(new GaodeMapAdapter()); // 成功适配!

2.2 后端数据格式适配

前端 UI 组件通常需要特定的数据格式,而后端返回的数据结构可能千奇百怪。

js
// 后端返回的原始数据
const oldData = [{ "day": "Monday", "val": 100 }, { "day": "Tuesday", "val": 200 }];

// UI组件需要的格式: { "Mon": 100, "Tue": 200 }
function dataAdapter(data) {
  const map = { "Monday": "Mon", "Tuesday": "Tue" };
  return data.reduce((acc, item) => {
    acc[map[item.day]] = item.val;
    return acc;
  }, {});
}

const uiData = dataAdapter(oldData);
console.log(uiData); // { Mon: 100, Tue: 200 }

3. 模式对比

适配器模式经常被拿来与代理、装饰器对比。

模式核心区别形象比喻
适配器模式转换接口。重点在于解决接口不兼容,让东西能动起来。电源转接头
代理模式控制访问。接口不变,重点在于控制谁能用、怎么用。明星经纪人
装饰器模式增强功能。接口不变,重点在于给原有对象加“ Buff ”。手机贴膜加壳

4. 常见问题 (FAQ)

4.1 什么时候该用适配器,什么时候该直接重构代码?

选择适用场景
使用适配器1. 原始代码极其复杂,改动风险极大;
2. 原始代码是第三方库或 SDK,你没有修改权限;
3. 只是临时过渡方案。
直接重构1. 项目处于早期阶段,修改成本低;
2. 接口设计极其不合理,适配器也救不了。

4.2 适配器模式会影响性能吗?

  • :在大多数 JS 应用场景中,多一层函数调用或对象封装带来的性能损耗是微秒级的,完全可以忽略不计。相比之下,它带来的代码可维护性提升要重要得多。

4.3 “包装模式” (Wrapper) 是指适配器吗?

  • :是的。在许多文献中,适配器模式也被称为 Wrapper 模式,因为它本质上就是给旧接口包了一层新外壳。

4.4 适配器可以适配多个对象吗?

  • :可以。这被称为组合适配器。你可以创建一个适配器,在内部调用多个不同的类,将它们聚合成一个统一的、更好用的接口暴露给外界。