前端中的适配器模式 vs 外观模式

适配器模式和外观模式是前端开发中最常用的两种"包装型"设计模式。很多开发者能写出转换函数和封装工具,却讲不清它们什么时候用、区别在哪。本文用六个真实场景把这两种模式拆开,对比它们的本质差异。

一、适配器模式:让不兼容的接口一起工作

适配器模式的核心是:将一个接口转换成客户期望的另一个接口,使原本不兼容的类可以一起工作。 它解决的是"已经有了一堆东西,但它们对接不上"的问题。

1. 统一后端接口数据格式

后端接口升级,返回结构全变了,而页面组件期望的仍是旧格式。在数据进入组件之前加一层适配器,组件零改动。

javascript 复制代码
// 适配器:把新接口数据转成组件期望的旧格式
function userAdapter(apiData) {
  const user = apiData.data;
  return {
    id: user.user_id,
    name: user.user_name,
    email: user.contact?.email_addr ?? '',
    phone: user.contact?.phone_num ?? '',
  };
}

// 使用
const adaptedUser = userAdapter(newApiResponse);
renderUserCard(adaptedUser);

价值:后端怎么改都行,只要更新适配器函数,所有组件无需变动。Axios 的请求/响应拦截器本质上也是适配器------在请求发出前和响应回来后做数据转换。

2. 多端 API 统一

同一个 H5 需要运行在浏览器、微信小程序、企业微信中,它们的存储 API 各不相同。用一个适配器对象统一对外接口:

javascript 复制代码
const storageEngines = {
  browser: {
    get(key) { return localStorage.getItem(key); },
    set(key, val) { localStorage.setItem(key, val); },
  },
  wechat: {
    get(key) { return wx.getStorageSync(key); },
    set(key, val) { wx.setStorageSync(key, val); },
  },
  wecom: {
    get(key) { return wx.qy.getStorageSync(key); },
    set(key, val) { wx.qy.setStorageSync(key, val); },
  },
};

function createStorage() {
  const engine = typeof wx?.qy !== 'undefined' ? 'wecom'
    : typeof wx !== 'undefined' ? 'wechat'
    : 'browser';
  return storageEngines[engine];
}

价值 :新增支付宝小程序,只需在 storageEngines 加一个 key,所有业务代码不动。

3. 封装第三方图表库

ECharts 和 Highcharts 初始化方式完全不同,通过适配器提供统一的 setOptionsdestroy 接口,业务组件不感知底层引擎。

javascript 复制代码
class EChartsAdapter {
  constructor(container) { this.chart = echarts.init(container); }
  setOptions(options) { this.chart.setOption(options); }
  destroy() { this.chart.dispose(); }
}
class HighchartsAdapter {
  constructor(container) { this.chart = new Highcharts.Chart(container, {}); }
  setOptions(options) { this.chart.update(options); }
  destroy() { this.chart.destroy(); }
}

价值:切换图表库只需改一个字符串,调用方代码完全不变。适配器让更换第三方库的成本降到最低。


二、外观模式:把复杂的变简单

外观模式的核心是:为复杂的子系统提供一个简化的统一接口,隐藏内部实现细节,让调用方更容易使用。 它解决的是"东西本身很好用,但用起来太麻烦"的问题。

1. 封装地图 SDK

高德地图初始化需要设置容器、中心点、添加控件,内部步骤繁琐。外观模式将其封装成几个简单方法,团队其他人无需学习 SDK 细节。

javascript 复制代码
class MapFacade {
  #map = null;
  init(containerId, { zoom = 12, center }) {
    this.#map = new AMap.Map(containerId, { zoom, center });
    this.#map.addControl(new AMap.Scale());
    this.#map.addControl(new AMap.ToolBar());
    return this;
  }
  addMarker(position, content, onClick) {
    const marker = new AMap.Marker({ position, content });
    if (onClick) marker.on('click', onClick);
    this.#map.add(marker);
    return marker;
  }
  destroy() { this.#map?.destroy(); }
}

价值:将来换成百度地图,只需改外观类内部,所有调用方不动。外观让复杂 SDK 变得"开箱即用"。

2. 统一浏览器存储 API

localStoragesessionStorageCookie 原生 API 各不相同,每次存取还需手动序列化。外观模式对外提供一致的 getset 接口:

javascript 复制代码
export const storage = {
  local: {
    get(key) {
      const val = localStorage.getItem(key);
      return val ? JSON.parse(val) : null;
    },
    set(key, value) { localStorage.setItem(key, JSON.stringify(value)); },
    remove(key) { localStorage.removeItem(key); },
  },
  session: {
    get(key) { /* 同理 */ },
    set(key, value) { /* 同理 */ },
  },
  cookie: {
    get(key) { /* 同上 */ },
    set(key, value, days = 7) { /* 同上 */ },
  },
};

价值:调用方不需要知道底层用哪种存储,也不用每次手动序列化。统一的 API 让代码更简洁。

3. 简化复杂操作流程

导出 PDF 需要调用 html2canvasjsPDF 两个库,涉及截图、生成、下载等多个步骤。外观函数将这些步骤打包成一个接口。

javascript 复制代码
export async function exportPDF(title, elementId) {
  const element = document.getElementById(elementId);
  const loading = showLoading('正在生成 PDF...');
  try {
    const canvas = await html2canvas(element, { scale: 2 });
    const pdf = new jsPDF('p', 'mm', 'a4');
    pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0, 210, 297);
    pdf.save(`${title}.pdf`);
  } finally {
    hideLoading(loading);
  }
}

价值 :调用方从需要了解两个库的复杂用法,变成一行 await exportPDF('报表', 'container')。外观让复杂操作像点外卖一样简单。


三、适配器 vs 外观:一张表讲清楚

对比维度 适配器模式 外观模式
核心目的 解决不兼容------让原本对接不上的接口能协作 解决太复杂------隐藏内部细节,提供简单入口
典型场景 新旧 API 格式统一、多端适配、第三方库接口转接 封装地图/图表 SDK、统一存储、简化复杂操作流程
接口变化 改变接口形状(A→B) 定义新的简化接口
类比 "翻译官"------把一种语言转成另一种 "遥控器"------按一个键就能完成复杂操作
前端内置 Axios 拦截器、Babel 转译、Vue 的 computed Vue 的 createApp、React 的 createRoot、Axios 本身

一个最简区分口诀:适配器是"换皮"------改变接口,解决兼容问题;外观是"减负"------降低使用门槛,让复杂变简单。如果你在两个已有接口之间做转换,那是适配器;如果你把一堆复杂的调用封装成一个简单方法,那是外观。

它们也可以组合使用:外观类的内部,可能使用适配器来对接多个不兼容的子系统;反过来,适配器也能作为一个更大外观的一部分。

五、总结

适配器和外观都做包装,但目的不同:

  • 适配器:已有两个东西对接不上 → 加一层转换,让它们能一起工作。
  • 外观:一个东西本身太复杂 → 加一层简化,让调用者更好用。

在日常开发中,当你写了一个函数去转换数据格式,那是适配器;当你封装了一个工具类让团队更容易上手,那是外观。把这两个模式分清楚,能让你的代码更优雅。

相关推荐
图扑软件1 小时前
其域创新 × 图扑软件|3DGS 高斯泼溅智慧钢厂实景孪生
前端·低代码·3d·编辑器·图形渲染·可视化
咖啡八杯1 小时前
GoF设计模式——责任链模式
设计模式·面试·架构
labixiong1 小时前
写给前端的 Generator 指南:从 yield 到 async/await 的完整进化
前端·javascript·前端框架
asdzx671 小时前
使用 Spire.WordJS 在 React 中实现在线 Word 文档编辑
前端·react.js·word
大大大大晴天1 小时前
聊聊Flink的几种血缘方案
前端·flink
消失的旧时光-19431 小时前
KMP Web 开发实战(二):js、wasmJs、browser、executable 到底是什么意思?
开发语言·前端·javascript·跨平台·kmp
宇图SHARE1 小时前
别再用 Python 写 Agent 前端了!Vue3 + NestJS + TypeScript 全栈方案,开发效率翻倍
前端·后端
IT_陈寒1 小时前
Python的GIL让我以为CPU跑满了,结果...
前端·人工智能·后端