目录

3.适配器模式

1. 适配器模式的本质

2. 业务场景实践:新旧网络请求接口兼容

场景背景

生产级应用:axios的跨环境适配器

适配器模式的核心价值

3. 案例练习:新旧本地存储工具适配

场景描述

TS实现的优越性体现

4.代理模式

1. 代理模式核心思想

2. 四种常见代理类型及实践

1. 事件代理(性能优化型)

2. 虚拟代理(职责解耦型)

3. 缓存代理(效率提升型)

4. 保护代理(权限控制型)

代理模式核心总结

3. 案例练习:商品库存查询代理

场景描述

TS 实现的优越性体现

3.适配器模式

1. 适配器模式的本质

适配器模式是结构型设计模式的一种,核心目标是解决「接口不兼容」问题:
将一个类/函数/接口的格式(输入参数、返回值、调用方式),转换为客户端(业务代码)期望的另一种格式,使得原本因格式不匹配而无法协作的组件,能够无缝衔接。
关键特点:客户端无需修改任何代码,只需通过适配器“中转”,即可使用新接口的能力。

2. 业务场景实践:新旧网络请求接口兼容

场景背景
  • 旧接口:基于XMLHttpRequestAjax函数(回调式),调用格式为:
    Ajax(type, url, data, successCallback, failCallback)
  • 新接口:基于fetchHttpUtils类(Promise式),调用格式为:
    await HttpUtils.get(url)await HttpUtils.post(url, data)
  • 痛点:业务中大量使用旧接口调用,手动修改成本极高。

适配器接收旧接口的参数格式,内部调用新接口,再将新接口的Promise结果转换为旧接口的回调逻辑,实现“旧调用方式用新接口能力”:

// 适配器:接收旧接口参数,内部调用新接口
async function AjaxAdapter(type, url, data, success, failed) {
  const method = type.toUpperCase();
  try {
    // 内部调用新接口(HttpUtils)
    const result = method === 'GET' 
      ? await HttpUtils.get(url) 
      : await HttpUtils.post(url, data);
    // 适配旧接口的回调逻辑
    result.statusCode === 1 ? success(result.data) : failed(result.statusCode);
  } catch (error) {
    failed(error.statusCode || '网络错误');
  }
}

// 覆盖旧接口:业务代码无需修改,仍调用Ajax
function Ajax(...args) {
  return AjaxAdapter(...args);
}

生产级应用:axios的跨环境适配器

axios的核心优势之一是「跨环境统一API」,其底层依赖适配器模式实现:

  • 目标:让用户在浏览器和Node.js中,用同一套axios.get/postAPI发起请求,无需关心底层差异;
  • 适配器逻辑
    1. 通过getDefaultAdapter函数判断执行环境:
      • 浏览器环境:使用xhr适配器(基于XMLHttpRequest);
      • Node环境:使用http适配器(基于Node内置http模块);
    1. 两个适配器对外暴露统一格式:入参为config对象(含method、url、data等),出参为Promise
  • 价值:用户无需学习两套API,适配器封装了所有底层差异,降低使用成本。

适配器模式的核心价值

  1. 解耦:客户端(业务代码)与新接口完全分离,无需感知新接口的存在;
  2. 复用:旧组件/代码无需改造,即可复用新接口的能力(如旧业务代码复用fetch/axios);
  3. 灵活:后续替换新接口(如从fetch切换到axios),只需修改适配器,无需改动业务代码。

3. 案例练习:新旧本地存储工具适配

场景描述

  • 旧存储工具(OldStorage):仅支持localStorage,回调式调用,格式如下:
    OldStorage.save(key, value, success, fail)(保存数据,成功/失败通过回调通知);
    OldStorage.get(key, success, fail)(获取数据,结果通过回调返回);
  • 新存储工具(NewStorage):支持localStorage/sessionStorage,Promise式调用,格式如下:
    NewStorage.save(storageType, key, value): Promise<void>(storageType可选'local'/'session');
    NewStorage.get(storageType, key): Promise<string | null>
  • 需求:不修改旧业务中对OldStorage的调用代码,通过适配器兼容NewStorage,且默认使用localStorage。
// 1. 旧存储工具(模拟历史代码,不可修改)
const OldStorage = {
  save: (key, value, success, fail) => {
    try {
      localStorage.setItem(key, value);
      success?.('保存成功');
    } catch (err) {
      fail?.(err.message);
    }
  },
  get: (key, success, fail) => {
    try {
      const value = localStorage.getItem(key);
      success?.(value);
    } catch (err) {
      fail?.(err.message);
    }
  }
};

// 2. 新存储工具(新实现,支持多存储类型+Promise)
const NewStorage = {
  // storageType: 'local' | 'session'
  save: (storageType, key, value) => {
    return new Promise((resolve, reject) => {
      try {
        const storage = storageType === 'session' ? sessionStorage : localStorage;
        storage.setItem(key, value);
        resolve('保存成功');
      } catch (err) {
        reject(err.message);
      }
    });
  },
  get: (storageType, key) => {
    return new Promise((resolve, reject) => {
      try {
        const storage = storageType === 'session' ? sessionStorage : localStorage;
        const value = storage.getItem(key);
        resolve(value);
      } catch (err) {
        reject(err.message);
      }
    });
  }
};

// 3. 适配器:适配OldStorage的调用格式到NewStorage
const OldStorageAdapter = {
  // 适配save方法:旧参数(key, value, success, fail)→ 内部调用新save(默认local)
  save: async (key, value, success, fail) => {
    try {
      const result = await NewStorage.save('local', key, value);
      success?.(result); // 回调旧接口的成功函数
    } catch (err) {
      fail?.(err); // 回调旧接口的失败函数
    }
  },
  // 适配get方法:同理
  get: async (key, success, fail) => {
    try {
      const value = await NewStorage.get('local', key);
      success?.(value);
    } catch (err) {
      fail?.(err);
    }
  }
};

// 4. 旧业务代码(无需修改,直接调用)
OldStorage.save = OldStorageAdapter.save;
OldStorage.get = OldStorageAdapter.get;

// 测试:旧调用方式仍可用,且实际用新存储工具
OldStorage.save('username', 'zhangsan', 
                (res) => console.log('旧调用成功:', res), // 输出:旧调用成功:保存成功
                (err) => console.log('旧调用失败:', err)
               );

OldStorage.get('username', 
               (val) => console.log('旧调用获取:', val), // 输出:旧调用获取:zhangsan
               (err) => console.log('旧调用失败:', err)
              );
// 1. 定义核心类型(TS核心:类型约束,减少错误)
type StorageType = 'local' | 'session'; // 限制存储类型,只能是两个值
type SuccessCallback<T = void> = (result: T) => void; // 成功回调类型(支持泛型)
type FailCallback = (error: string) => void; // 失败回调类型

// 2. 旧存储工具(类型化,明确参数/返回值)
const OldStorage = {
  save: (
    key: string, 
    value: string, 
    success?: SuccessCallback<string>, 
    fail?: FailCallback
  ) => {
    try {
      localStorage.setItem(key, value);
      success?.('保存成功');
    } catch (err) {
      fail?.((err as Error).message); // TS类型断言,避免类型错误
    }
  },
  get: (
    key: string, 
    success?: SuccessCallback<string | null>, 
    fail?: FailCallback
  ) => {
    try {
      const value = localStorage.getItem(key);
      success?.(value);
    } catch (err) {
      fail?.((err as Error).message);
    }
  }
};

// 3. 新存储工具(类型化,参数/返回值清晰)
const NewStorage = {
  save: (
    storageType: StorageType, 
    key: string, 
    value: string
  ): Promise<string> => { // 明确返回Promise<string>
    return new Promise((resolve, reject) => {
      try {
        const storage = storageType === 'session' ? sessionStorage : localStorage;
        storage.setItem(key, value);
        resolve('保存成功');
      } catch (err) {
        reject((err as Error).message);
      }
    });
  },
  get: (
    storageType: StorageType, 
    key: string
  ): Promise<string | null> => { // 明确返回Promise<string | null>
    return new Promise((resolve, reject) => {
      try {
        const storage = storageType === 'session' ? sessionStorage : localStorage;
        const value = storage.getItem(key);
        resolve(value);
      } catch (err) {
        reject((err as Error).message);
      }
    });
  }
};

// 4. 适配器(严格遵循旧接口类型,防止参数错误)
const OldStorageAdapter = {
  save: async (
    key: string, 
    value: string, 
    success?: SuccessCallback<string>, 
    fail?: FailCallback
  ): Promise<void> => { // 明确返回Promise<void>
    try {
      const result = await NewStorage.save('local', key, value);
      success?.(result);
    } catch (err) {
      fail?.(err as string); // TS类型约束,确保err是string
    }
  },
  get: async (
    key: string, 
    success?: SuccessCallback<string | null>, 
    fail?: FailCallback
  ): Promise<void> => {
    try {
      const value = await NewStorage.get('local', key);
      success?.(value);
    } catch (err) {
      fail?.(err as string);
    }
  }
};

// 5. 旧业务代码(类型安全,编辑器有提示)
OldStorage.save = OldStorageAdapter.save;
OldStorage.get = OldStorageAdapter.get;

// 测试:TS会自动提示参数类型,错误传参直接报错
OldStorage.save(
  'username', // 必须是string,传number会报错
  'zhangsan', 
  (res) => console.log('旧调用成功:', res), // res自动提示是string
  (err) => console.log('旧调用失败:', err) // err自动提示是string
);

OldStorage.get(
  'username', 
  (val) => console.log('旧调用获取:', val), // val自动提示是string | null
  (err) => console.log('旧调用失败:', err)
);

TS实现的优越性体现

  1. 类型约束:通过StorageTypeSuccessCallback等类型,限制参数取值范围(如storageType只能是'local'/'session'),防止传错类型(如传'cookie'直接报错);
  2. 代码提示:编辑器会自动提示函数参数类型、返回值类型(如success回调的resultstring),减少记忆成本;
  3. 错误提前暴露:若旧业务代码误传参数(如OldStorage.save(123, 'zhangsan', ...),123是number),TS编译阶段直接报错,避免运行时bug;
  4. 可维护性:类型定义即文档,新同事接手时,通过类型就能明确函数的输入输出,无需通读逻辑。

4.代理模式

1. 代理模式核心思想

  • 本质通过「代理器」实现间接访问,避免对象 A 直接操作对象 B
  • 核心目标:解耦职责、提升性能、增强权限控制、优化代码结构
  • 通用套路:访问链路为「A → 代理器 → B」,代理器承担中间调度、逻辑增强或权限校验工作

2. 四种常见代理类型及实践

1. 事件代理(性能优化型)
  • 适用场景:父元素下有多个子元素需绑定相同事件(如列表项点击、导航链接跳转)
  • 实现原理:利用 DOM 事件的「冒泡机制」,仅给父元素绑定 1 次事件,通过 e.target 识别触发事件的子元素
  • 核心优势:减少事件监听数量(避免 N 个子元素绑定 N 个事件),尤其适合子元素动态增减的场景(无需重新绑定事件)
// 父元素(仅绑定1次事件)
const father = document.getElementById('father');
father.addEventListener('click', (e) => {
  // 识别目标子元素(仅处理 <a> 标签的点击)
  if (e.target.tagName.toLowerCase() === 'a') {
    e.preventDefault();
    alert(`点击了:${e.target.innerText}`);
  }
});
2. 虚拟代理(职责解耦型)
  • 适用场景:需分离「核心功能」与「辅助功能」(如图片预加载、异步请求封装)
  • 实现原理:用「虚拟对象」(如内存中的 Image 实例、异步请求封装层)承担辅助逻辑,真实对象仅负责核心职责(如 DOM 渲染、数据处理)
  • 核心优势:符合「单一职责原则」,避免一个类同时处理多个无关逻辑(如图片类既管 DOM 展示又管加载)
// 真实对象:仅处理 DOM 图片设置(核心职责)
class RealImage {
  constructor(imgNode) { this.imgNode = imgNode; }
  setSrc(url) { this.imgNode.src = url; }
}

// 虚拟代理:处理图片预加载(辅助逻辑)
class ProxyImage {
  static LOADING_URL = 'https://placeholder.com/loading'; // 占位图
  constructor(realImage) { this.realImage = realImage; }

  setSrc(targetUrl) {
    // 步骤1:先显示占位图
    this.realImage.setSrc(ProxyImage.LOADING_URL);
    // 步骤2:用虚拟 Image 加载真实图片
    const virtualImg = new Image();
    virtualImg.onload = () => {
      // 步骤3:加载完成后更新真实 DOM 图片
      this.realImage.setSrc(targetUrl);
    };
    virtualImg.src = targetUrl; // 触发加载
  }
}
3. 缓存代理(效率提升型)
  • 适用场景:计算量大、重复计算频繁的场景(如多参数求和、数据格式化、复杂列表筛选)
  • 实现原理:用「缓存池」(对象/Map)存储「入参 → 结果」的映射,重复入参直接返回缓存结果,避免二次计算
  • 核心优势:用空间换时间,显著减少重复计算的性能开销
// 核心计算函数
const sum = (...args) => {
  console.log('执行新计算'); // 仅首次触发
  return args.reduce((total, cur) => total + cur, 0);
};

// 缓存代理
const proxySum = (() => {
  const cache = {}; // 缓存池:key=入参字符串,value=计算结果
  return (...args) => {
    // 生成入参唯一标识(避免 [1,2] 和 [2,1] 被误判为不同)
    const argsKey = args.sort((a, b) => a - b).join(',');
    // 缓存命中则返回,否则计算并存储
    return cache[argsKey] ?? (cache[argsKey] = sum(...args));
  };
})();

// 测试:重复入参仅计算1次
console.log(proxySum(1, 2, 3)); // 执行新计算 → 6
console.log(proxySum(3, 2, 1)); // 从缓存获取 → 6
4. 保护代理(权限控制型)
  • 适用场景:需限制对目标对象的访问权限(如用户信息脱敏、VIP 功能拦截、敏感操作校验)
  • 实现原理:在代理器中添加权限校验逻辑,仅当满足条件(如登录、VIP 身份)时,才允许访问目标对象的属性/方法
  • 核心优势:保障目标对象安全,避免非法访问或误操作
// 目标对象(敏感用户信息)
const user = { name: '张三', phone: '138xxxx8888', vipLevel: 2 };

// 保护代理(ES6 Proxy 实现)
const userProxy = new Proxy(user, {
  get(target, key) {
    // 未登录时禁止访问手机号
    if (!isLogin() && key === 'phone') {
      throw new Error('请登录后查看联系方式');
    }
    // 非VIP用户隐藏手机号中间4位
    if (target.vipLevel < 1 && key === 'phone') {
      return target.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
    }
    return target[key];
  }
});

// 模拟登录状态
let isLogin = () => false;
console.log(userProxy.phone); // 报错:请登录后查看联系方式

isLogin = () => true;
console.log(userProxy.phone); // 138****8888(非VIP脱敏)

代理模式核心总结

  • 共性:所有代理类型均通过「中间层(代理器)」实现间接访问,核心是「分离访问与实现」
  • 差异:代理器的目标不同(性能优化、职责解耦、效率提升、权限控制)
  • 适用场景:当需要增强控制、拆分复杂逻辑、优化性能时,优先考虑代理模式

3. 案例练习:商品库存查询代理

场景描述

实现电商商品库存查询功能:

  1. 从接口查询商品库存(模拟异步请求,每次请求耗时 500ms)
  2. 相同商品 ID 的查询结果缓存 10 秒(避免重复请求,缓存代理)
  3. 仅管理员可查看「真实库存」,普通用户仅显示「有货/无货」(权限控制,保护代理)
// 1. 模拟用户状态与权限(实际项目从全局状态/后端获取)
const user = { role: 'user' }; // role: 'user'(普通用户)/ 'admin'(管理员)

// 2. 模拟异步库存查询接口(耗时500ms)
const fetchStock = (goodsId) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      // 模拟不同商品的真实库存
      const stockMap = { 101: 23, 102: 0, 103: 56 };
      resolve(stockMap[goodsId] ?? 0); // 无对应商品返回0
    }, 500);
  });
};

// 3. 库存查询代理(整合缓存代理 + 保护代理)
const stockProxy = (() => {
  const cache = new Map(); // 缓存池:key=goodsId,value={stock, expireTime}

  return async (goodsId) => {
    const now = Date.now();
    const cacheKey = goodsId.toString();

    // 1. 缓存代理:检查缓存是否有效(未过期)
    if (cache.has(cacheKey)) {
      const { stock, expireTime } = cache.get(cacheKey);
      if (now < expireTime) {
        console.log(`[缓存命中] 商品${goodsId}的库存`);
        return formatStock(stock); // 格式化后返回
      }
      cache.delete(cacheKey); // 缓存过期,删除
    }

    // 2. 调用真实接口查询库存
    console.log(`[接口请求] 商品${goodsId}的库存`);
    const realStock = await fetchStock(goodsId);

    // 3. 存储缓存(有效期10秒)
    cache.set(cacheKey, {
      stock: realStock,
      expireTime: now + 10 * 1000 // 10秒后过期
    });

    // 4. 保护代理:按用户权限格式化库存
    return formatStock(realStock);
  };

  // 辅助函数:按权限格式化库存
  function formatStock(realStock) {
    if (user.role === 'admin') {
      return `真实库存:${realStock}件`;
    }
    // 普通用户仅显示“有货/无货”
    return realStock > 0 ? '库存状态:有货' : '库存状态:无货';
  }
})();

// 测试
(async () => {
  // 普通用户查询(首次请求)
  console.log(await stockProxy(101)); // [接口请求] → 库存状态:有货
  // 普通用户重复查询(缓存命中)
  console.log(await stockProxy(101)); // [缓存命中] → 库存状态:有货

  // 切换为管理员
  user.role = 'admin';
  // 管理员查询(缓存未过期)
  console.log(await stockProxy(101)); // [缓存命中] → 真实库存:23件

  // 等待10秒后查询(缓存过期)
  setTimeout(async () => {
    console.log(await stockProxy(101)); // [接口请求] → 真实库存:23件
  }, 10 * 1000);
})();

// 1. 定义接口:明确类型约束,避免参数错误
interface User {
  role: 'user' | 'admin'; // 仅允许两种角色,避免非法值
}

interface CacheItem {
  stock: number;         // 库存数量(数字类型)
  expireTime: number;    // 过期时间(时间戳)
}

// 2. 模拟用户状态(类型明确,避免role传错值)
const user: User = { role: 'user' };

// 3. 模拟异步库存查询接口(参数和返回值类型明确)
const fetchStock = async (goodsId: number): Promise<number> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const stockMap: Record<number, number> = { 101: 23, 102: 0, 103: 56 };
      resolve(stockMap[goodsId] ?? 0);
    }, 500);
  });
};

// 4. 库存查询代理(类型约束确保逻辑安全)
const stockProxy = (() => {
  const cache = new Map<string, CacheItem>(); // 缓存池类型明确

  // 代理函数:入参为number(商品ID),返回值为Promise<string>(格式化结果)
  const proxyFn = async (goodsId: number): Promise<string> => {
    const now = Date.now();
    const cacheKey = goodsId.toString();

    // 1. 缓存校验(TS确保cache的get/set方法参数类型正确)
    if (cache.has(cacheKey)) {
      const { stock, expireTime } = cache.get(cacheKey)!; // 非null断言(has已判断存在)
      if (now < expireTime) {
        console.log(`[缓存命中] 商品${goodsId}的库存`);
        return formatStock(stock);
      }
      cache.delete(cacheKey);
    }

    // 2. 接口请求(TS确保await的是Promise类型)
    console.log(`[接口请求] 商品${goodsId}的库存`);
    const realStock = await fetchStock(goodsId);

    // 3. 存储缓存(TS确保CacheItem结构正确)
    cache.set(cacheKey, {
      stock: realStock,
      expireTime: now + 10 * 1000
    });

    // 4. 权限格式化
    return formatStock(realStock);
  };

  // 辅助函数:按权限格式化(TS确保入参是number,返回值是string)
  const formatStock = (realStock: number): string => {
    if (user.role === 'admin') {
      return `真实库存:${realStock}件`;
    }
    return realStock > 0 ? '库存状态:有货' : '库存状态:无货';
  };

  return proxyFn;
})();

// 测试(TS确保调用方式正确,避免传非number类型的goodsId)
(async () => {
  console.log(await stockProxy(101)); // 普通用户:有货(接口请求)
  console.log(await stockProxy(101)); // 普通用户:有货(缓存命中)

  user.role = 'admin';
  console.log(await stockProxy(101)); // 管理员:23件(缓存命中)

  // 10秒后缓存过期
  setTimeout(async () => {
    console.log(await stockProxy(101)); // 管理员:23件(接口请求)
  }, 10 * 1000);

  // 错误示例:TS编译时会报错(goodsId必须是number,不能是string)
  // console.log(await stockProxy('101'));
})();

TS 实现的优越性体现

  1. 类型约束:通过 interface 定义 UserCacheItem,限制 user.role 仅能为 useradmin,避免传入非法角色(如 guest)导致的逻辑错误。
  2. 参数安全:函数 fetchStockstockProxy 的参数类型明确为 number(商品 ID),TS 编译时会拦截非数字类型的调用(如 stockProxy('101') 直接报错)。
  3. 开发体验:IDE 可提供自动补全(如输入 user. 时提示 role 属性)和实时错误提示(如少传参数、类型不匹配时立即高亮),降低调试成本。
  4. 代码可读性:接口定义清晰描述了数据结构(如 CacheItem 包含 stockexpireTime),其他开发者无需阅读业务逻辑即可理解参数含义,提升协作效率。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐