阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 适配器模式(Adapter)的接口转换实践

适配器模式(Adapter)的接口转换实践

作者:陈川 阅读数:3423人阅读 分类: JavaScript

适配器模式的核心思想

适配器模式是一种结构型设计模式,它允许不兼容的接口之间进行协作。就像现实世界中的电源适配器可以让不同国家的插头工作一样,在编程中,适配器充当两个不兼容接口之间的桥梁。这种模式特别有用当我们需要使用某个类,但其接口与其他代码不兼容时。

JavaScript中的适配器实现方式

在JavaScript中实现适配器模式主要有两种方式:

  1. 类适配器:通过继承来适配接口
  2. 对象适配器:通过组合来适配接口

由于JavaScript的多重继承支持有限,对象适配器更为常见。下面是一个简单的对象适配器示例:

// 目标接口
class Target {
  request() {
    return 'Target: 默认行为';
  }
}

// 需要适配的类
class Adaptee {
  specificRequest() {
    return '.eetpadA eht fo roivaheb laicepS';
  }
}

// 适配器
class Adapter extends Target {
  constructor(adaptee) {
    super();
    this.adaptee = adaptee;
  }

  request() {
    const result = this.adaptee.specificRequest().split('').reverse().join('');
    return `Adapter: (转换后) ${result}`;
  }
}

// 使用
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request()); // 输出: Adapter: (转换后) Special behavior of the Adaptee.

实际应用场景

第三方库集成

当引入第三方库时,其API可能与我们现有系统的接口不匹配。例如,我们有一个统一的日志系统接口:

class Logger {
  log(message) {
    console.log(`[LOG] ${message}`);
  }
}

但新引入的第三方日志库使用不同的方法签名:

class ThirdPartyLogger {
  print(msg) {
    console.log(`[THIRD PARTY] ${msg}`);
  }
}

这时可以创建适配器:

class LoggerAdapter extends Logger {
  constructor(thirdPartyLogger) {
    super();
    this.thirdPartyLogger = thirdPartyLogger;
  }

  log(message) {
    this.thirdPartyLogger.print(message);
  }
}

// 使用
const thirdPartyLogger = new ThirdPartyLogger();
const logger = new LoggerAdapter(thirdPartyLogger);
logger.log('适配器模式真有用!'); // 输出: [THIRD PARTY] 适配器模式真有用!

数据格式转换

另一个常见场景是数据格式转换。假设我们的系统期望用户数据格式为:

{
  fullName: '张三',
  age: 30
}

但后端API返回的数据格式是:

{
  name: '张三',
  years: 30
}

可以创建用户数据适配器:

class UserAdapter {
  constructor(apiData) {
    this.apiData = apiData;
  }

  get formattedUser() {
    return {
      fullName: this.apiData.name,
      age: this.apiData.years
    };
  }
}

// 使用
const apiResponse = { name: '李四', years: 25 };
const adaptedUser = new UserAdapter(apiResponse).formattedUser;
console.log(adaptedUser); // 输出: { fullName: '李四', age: 25 }

高级适配器模式应用

多适配器系统

在复杂系统中,可能需要多个适配器来处理不同的接口变体。例如,处理不同地图API的适配器:

// 统一地图接口
class Map {
  display(lat, lng) {}
}

// Google Maps适配器
class GoogleMapsAdapter extends Map {
  constructor(googleMaps) {
    super();
    this.googleMaps = googleMaps;
  }

  display(lat, lng) {
    this.googleMaps.show({ latitude: lat, longitude: lng });
  }
}

// Baidu Maps适配器
class BaiduMapsAdapter extends Map {
  constructor(baiduMaps) {
    super();
    this.baiduMaps = baiduMaps;
  }

  display(lat, lng) {
    this.baiduMaps.render(lat, lng);
  }
}

// 使用
function displayLocation(map, lat, lng) {
  map.display(lat, lng);
}

const googleMap = new GoogleMapsAdapter(externalGoogleMaps);
const baiduMap = new BaiduMapsAdapter(externalBaiduMaps);

displayLocation(googleMap, 39.9042, 116.4074);
displayLocation(baiduMap, 31.2304, 121.4737);

函数适配器

适配器模式也可以用于函数级别,特别是在处理回调函数或事件处理器时:

// 原始函数
function oldApi(callback) {
  // 模拟异步操作
  setTimeout(() => {
    callback(null, { data: '老API返回的数据' });
  }, 1000);
}

// 新API期望Promise
function newApi() {
  return new Promise((resolve, reject) => {
    oldApi((err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

// 或者创建更通用的适配器函数
function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  };
}

const adaptedOldApi = promisify(oldApi);
adaptedOldApi().then(data => console.log(data)); // 输出: { data: '老API返回的数据' }

适配器模式的优缺点

优点

  1. 单一职责原则:可以将接口转换代码从业务逻辑中分离出来
  2. 开闭原则:无需修改现有代码就能引入新的适配器
  3. 提高复用性:现有类可以在不同接口的系统中复用
  4. 解耦:客户端代码与具体实现解耦

缺点

  1. 增加复杂性:引入额外层会增加代码复杂度
  2. 性能开销:间接调用可能带来轻微性能损失
  3. 过度使用:可能导致系统中有过多小类,难以维护

与其他模式的关系

  1. 与装饰器模式:适配器改变对象接口,装饰器增强对象功能
  2. 与外观模式:适配器使已有接口可用,外观定义新接口
  3. 与代理模式:适配器转换接口,代理控制访问

实际项目中的考量

在实现适配器时,需要考虑以下因素:

  1. 接口差异程度:差异越大,适配器可能越复杂
  2. 性能影响:数据转换可能成为瓶颈
  3. 维护成本:需要维护适配器与被适配代码的同步
  4. 测试策略:适配器需要充分测试以确保正确转换

例如,在React应用中处理不同表单库的适配:

// 假设我们有两个表单库
class FormikForm {
  getValues() {
    return { /* Formik格式的数据 */ };
  }
}

class FinalForm {
  getFormState() {
    return { /* Final Form格式的数据 */ };
  }
}

// 创建统一表单适配器
class FormAdapter {
  constructor(formInstance) {
    this.form = formInstance;
  }

  getFormData() {
    if (this.form instanceof FormikForm) {
      return this.transformFormikData(this.form.getValues());
    } else if (this.form instanceof FinalForm) {
      return this.transformFinalFormData(this.form.getFormState());
    }
    throw new Error('不支持的表格类型');
  }

  transformFormikData(data) {
    // 转换Formik数据到统一格式
    return { /* 统一格式数据 */ };
  }

  transformFinalFormData(data) {
    // 转换Final Form数据到统一格式
    return { /* 统一格式数据 */ };
  }
}

// 使用
const formikForm = new FormikForm();
const finalForm = new FinalForm();

const formikAdapter = new FormAdapter(formikForm);
const finalFormAdapter = new FormAdapter(finalForm);

const unifiedData1 = formikAdapter.getFormData();
const unifiedData2 = finalFormAdapter.getFormData();

浏览器API适配

现代Web开发中经常需要处理浏览器API差异,适配器模式非常适合这种场景:

// 统一存储接口
class Storage {
  setItem(key, value) {}
  getItem(key) {}
}

// LocalStorage适配器
class LocalStorageAdapter extends Storage {
  setItem(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  }

  getItem(key) {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : null;
  }
}

// IndexedDB适配器
class IndexedDBAdapter extends Storage {
  constructor(dbName, storeName) {
    super();
    this.dbName = dbName;
    this.storeName = storeName;
    this.db = null;
  }

  async init() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: 'key' });
        }
      };
      
      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve();
      };
      
      request.onerror = (event) => {
        reject(event.target.error);
      };
    });
  }

  setItem(key, value) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(this.storeName, 'readwrite');
      const store = transaction.objectStore(this.storeName);
      
      const request = store.put({ key, value });
      
      request.onsuccess = () => resolve();
      request.onerror = (event) => reject(event.target.error);
    });
  }

  getItem(key) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(this.storeName, 'readonly');
      const store = transaction.objectStore(this.storeName);
      
      const request = store.get(key);
      
      request.onsuccess = () => resolve(request.result?.value || null);
      request.onerror = (event) => reject(event.target.error);
    });
  }
}

// 使用
async function useStorage() {
  const localStorageAdapter = new LocalStorageAdapter();
  localStorageAdapter.setItem('test', { a: 1 });
  console.log(localStorageAdapter.getItem('test')); // { a: 1 }

  const indexedDBAdapter = new IndexedDBAdapter('MyDB', 'MyStore');
  await indexedDBAdapter.init();
  await indexedDBAdapter.setItem('test', { b: 2 });
  const data = await indexedDBAdapter.getItem('test');
  console.log(data); // { b: 2 }
}

useStorage();

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌