Adapter Pattern

适配器模式的实现与应用场景

问题

什么是适配器模式?在前端开发中如何应用?

解答

适配器模式将一个接口转换成另一个接口,使原本不兼容的类可以一起工作。

基础示例:统一不同地图 API

// 旧的地图服务
class OldMapService {
  showLocation(lat, lng) {
    console.log(`OldMap: 显示位置 (${lat}, ${lng})`);
  }
}

// 新的地图服务,接口不同
class NewMapService {
  display(coordinates) {
    console.log(`NewMap: 显示位置 ${JSON.stringify(coordinates)}`);
  }
}

// 适配器:让新服务兼容旧接口
class MapAdapter {
  constructor(newMapService) {
    this.map = newMapService;
  }

  // 适配旧接口
  showLocation(lat, lng) {
    const coordinates = { latitude: lat, longitude: lng };
    this.map.display(coordinates);
  }
}

// 使用
const oldMap = new OldMapService();
const newMap = new NewMapService();
const adaptedMap = new MapAdapter(newMap);

// 统一调用方式
oldMap.showLocation(39.9, 116.4);      // OldMap: 显示位置 (39.9, 116.4)
adaptedMap.showLocation(39.9, 116.4);  // NewMap: 显示位置 {"latitude":39.9,"longitude":116.4}

实际应用:统一 HTTP 请求库

// axios 风格的请求
class AxiosLikeHttp {
  get(url, config) {
    return fetch(url, { ...config, method: 'GET' }).then(res => res.json());
  }
  
  post(url, data, config) {
    return fetch(url, {
      ...config,
      method: 'POST',
      body: JSON.stringify(data),
      headers: { 'Content-Type': 'application/json' }
    }).then(res => res.json());
  }
}

// jQuery 风格的请求
class JQueryLikeHttp {
  ajax(options) {
    return fetch(options.url, {
      method: options.type || 'GET',
      body: options.data ? JSON.stringify(options.data) : undefined,
      headers: options.data ? { 'Content-Type': 'application/json' } : {}
    }).then(res => res.json());
  }
}

// 适配器:将 jQuery 风格转为 axios 风格
class HttpAdapter {
  constructor(jqueryHttp) {
    this.http = jqueryHttp;
  }

  get(url, config) {
    return this.http.ajax({ url, type: 'GET', ...config });
  }

  post(url, data, config) {
    return this.http.ajax({ url, type: 'POST', data, ...config });
  }
}

// 使用统一接口
const http = new HttpAdapter(new JQueryLikeHttp());
http.get('/api/users');
http.post('/api/users', { name: 'Tom' });

数据格式适配

// 后端返回的数据格式
const apiResponse = {
  user_name: 'john_doe',
  user_age: 25,
  created_at: '2024-01-01'
};

// 前端期望的格式
// { userName: 'john_doe', userAge: 25, createdAt: '2024-01-01' }

// 数据适配器
function createDataAdapter(keyMap) {
  return function adapt(data) {
    const result = {};
    for (const [oldKey, newKey] of Object.entries(keyMap)) {
      if (data.hasOwnProperty(oldKey)) {
        result[newKey] = data[oldKey];
      }
    }
    return result;
  };
}

// 使用
const userAdapter = createDataAdapter({
  user_name: 'userName',
  user_age: 'userAge',
  created_at: 'createdAt'
});

const userData = userAdapter(apiResponse);
console.log(userData);
// { userName: 'john_doe', userAge: 25, createdAt: '2024-01-01' }

关键点

  • 适配器不改变原有类,通过包装实现接口转换
  • 常用于统一第三方库接口、兼容旧代码、数据格式转换
  • 适配器与被适配对象是组合关系,不是继承
  • 过度使用会增加系统复杂度,优先考虑重构
  • 前端常见场景:API 封装、数据格式化、事件处理兼容