🚀【前端救星】这个JS设计模式,让90%的开发者告别接口地狱,代码优雅度飙升!

67 阅读5分钟

开头钩子:当接口不一致成为开发噩梦

你有没有遇到过这样的场景:项目需要接入第三个支付平台,但每个平台的API接口千差万别;或者系统要支持多种数据库,但每个数据库的查询语法都不相同。每次新增一个服务,都要重写大量业务逻辑,代码变得越来越臃肿难维护。

这就是适配器模式要解决的痛点。今天,我将带你深入理解JS适配器模式,掌握这个让代码保持整洁、扩展性极强的设计模式。

一、适配器模式原理解析

什么是适配器模式?

适配器模式(Adapter Pattern)属于结构型设计模式,它的核心思想是:将一个类的接口转换成客户期望的另一个接口。适配器让那些接口不兼容的类可以一起工作。

适配器的三种角色

  1. 目标接口(Target):客户端期望的接口
  2. 适配者(Adaptee):需要被适配的现有接口
  3. 适配器(Adapter):将适配者接口转换为目标接口

工作原理图解

客户端 → 调用 → 目标接口 → 适配器 → 转换调用 → 适配者接口

二、JS适配器实现思路

类适配器 vs 对象适配器

在JavaScript中,我们主要使用对象适配器,因为JS基于原型继承的特性更适合这种方式。

基础实现模板

// 目标接口 - 使用抽象类确保接口契约
class Target {
  request() {
    throw new Error('必须实现request方法');
  }
}

// 适配者
class Adaptee {
  specificRequest() {
    return '适配者的特定请求';
  }
}

// 适配器 - 增加类型检查和错误处理
class Adapter extends Target {
  constructor(adaptee) {
    super();
    if (!adaptee || typeof adaptee.specificRequest !== 'function') {
      throw new Error('适配者必须提供specificRequest方法');
    }
    this.adaptee = adaptee;
  }

  request() {
    try {
      const result = this.adaptee.specificRequest();
      return `转换后的结果: ${result}`;
    } catch (error) {
      throw new Error(`适配器转换失败: ${error.message}`);
    }
  }
}


// 使用示例
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request()); // 输出: 转换后的结果: 适配者的特定请求

三、适配器模式实战技巧

技巧1:接口归一化

将多个不同接口统一为相同的方法签名,减少客户端代码的复杂性。

class UnifiedInterface {
  execute() {
    throw new Error('必须实现execute方法');
  }
}

技巧2:错误处理适配

对不同服务的错误响应进行统一格式化处理。

class ErrorAdapter {
  static adaptError(error) {
    if (error.code) {
      return { success: false, message: `错误代码: ${error.code}` };
    }
    return { success: false, message: error.message };
  }
}

技巧3:异步操作适配

处理不同异步模式的兼容性问题。

class PromiseAdapter {
  static fromCallback(fn) {
    return (...args) => {
      return new Promise((resolve, reject) => {
        fn(...args, (error, result) => {
          if (error) reject(error);
          else resolve(result);
        });
      });
    };
  }
}

技巧4:参数转换适配

统一不同服务的参数格式要求。

class ParamAdapter {
  static adaptPaymentParams(originalParams, provider) {
    const baseParams = {
      amount: originalParams.amount,
      currency: originalParams.currency || 'CNY'
    };

    switch (provider) {
      case 'alipay':
        return { ...baseParams, subject: originalParams.description };
      case 'wechat':
        return { ...baseParams, body: originalParams.description };
      default:
        return baseParams;
    }
  }
}

四、5个实战案例详解

案例1:多种支付接口适配

// 目标支付接口
class PaymentService {
  async pay(amount, orderId) {
    throw new Error('必须实现pay方法');
  }
}

// 支付宝支付适配器
class AlipayAdapter extends PaymentService {
  constructor(alipayClient, config = {}) {
    super();
    this.client = alipayClient;
    this.maxRetries = config.maxRetries || 3;
    this.retryDelay = config.retryDelay || 1000;
  }

  async pay(amount, orderId, options = {}) {
    this.validateParams(amount, orderId);

    const alipayParams = {
      total_amount: this.formatAmount(amount),
      out_trade_no: orderId,
      subject: options.subject || `订单支付-${orderId}`,
      time_expire: this.calculateExpiryTime(options.timeout)
    };

    return await this.executeWithRetry(() => 
      this.client.createOrder(alipayParams)
    );
  }

  validateParams(amount, orderId) {
    if (typeof amount !== 'number' || amount <= 0) {
      throw new Error('金额必须为正数');
    }
    if (!orderId || typeof orderId !== 'string') {
      throw new Error('订单ID必须为非空字符串');
    }
  }

  formatAmount(amount) {
    return amount.toFixed(2); // 保留两位小数
  }

  async executeWithRetry(operation, retries = this.maxRetries) {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        if (attempt === retries) throw error;
        await this.delay(this.retryDelay * attempt);
      }
    }
  }

  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}


// 微信支付适配器
class WechatPayAdapter extends PaymentService {
  constructor(wechatClient) {
    super();
    this.client = wechatClient;
  }

  async pay(amount, orderId) {
    const wechatParams = {
      total_fee: amount * 100, // 微信以分为单位
      out_trade_no: orderId,
      body: `订单支付-${orderId}`
    };
    
    return await this.client.unifiedOrder(wechatParams);
  }
}

// 使用示例
const paymentService = new AlipayAdapter(alipayClient);
await paymentService.pay(100, 'ORDER123');

案例2:多种数据库适配

// 统一数据库接口
class DatabaseService {
  async query(sql, params) {
    throw new Error('必须实现query方法');
  }
  async transaction(callback) {
        throw new Error('必须实现transaction方法');
    }
}

// MySQL适配器
class MySQLAdapter extends DatabaseService {
  constructor(mysqlPool) {
    super();
    this.pool = mysqlPool;
  }

  async query(sql, params) {
    const connection = await this.getConnection();
    try {
      return await this.executeQuery(connection, sql, params);
    } finally {
      connection.release();
    }
  }

  async transaction(callback) {
    const connection = await this.getConnection();
    try {
      await connection.beginTransaction();
      const result = await callback(connection);
      await connection.commit();
      return result;
    } catch (error) {
      await connection.rollback();
      throw error;
    } finally {
      connection.release();
    }
  }

  async getConnection() {
    return new Promise((resolve, reject) => {
      this.pool.getConnection((error, connection) => {
        if (error) reject(error);
        else resolve(connection);
      });
    });
  }

  executeQuery(connection, sql, params) {
    return new Promise((resolve, reject) => {
      connection.query(sql, params, (error, results) => {
        if (error) reject(error);
        else resolve(results);
      });
    });
  }
}


// MongoDB适配器
class MongoDBAdapter extends DatabaseService {
  constructor(mongoCollection) {
    super();
    this.collection = mongoCollection;
  }

  async query(sql, params) {
    // 将SQL转换为MongoDB查询
    const [operation, conditions] = this.parseSQL(sql);
    
    switch (operation) {
      case 'SELECT':
        return await this.collection.find(conditions).toArray();
      case 'INSERT':
        return await this.collection.insertOne(params);
      case 'UPDATE':
        return await this.collection.updateOne(conditions, { $set: params });
      default:
        throw new Error(`不支持的SQL操作: ${operation}`);
    }
  }

  parseSQL(sql) {
    // 简化的SQL解析逻辑
    const upperSQL = sql.toUpperCase();
    if (upperSQL.startsWith('SELECT')) {
      return ['SELECT', this.extractConditions(sql)];
    }
    // 其他操作解析...
  }
}

案例3:第三方API服务适配

// 统一天气服务接口
class WeatherService {
  async getWeather(city) {
    throw new Error('必须实现getWeather方法');
  }
}

// 和风天气适配器
class HeWeatherAdapter extends WeatherService {
  constructor(heWeatherClient) {
    super();
    this.client = heWeatherClient;
  }

  async getWeather(city) {
    const response = await this.client.weatherNow(city);
    
    // 统一响应格式
    return {
      temperature: response.now.temp,
      description: response.now.text,
      humidity: response.now.humidity,
      windSpeed: response.now.windSpeed
    };
  }
}

// 心知天气适配器
class SeniverseAdapter extends WeatherService {
  constructor(seniverseClient) {
    super();
    this.client = seniverseClient;
  }

  async getWeather(city) {
    const response = await this.client.getWeather(city);
    
    return {
      temperature: response.temperature,
      description: response.weather,
      humidity: response.humidity,
      windSpeed: response.wind_speed
    };
  }
}

案例4:日志服务适配

// 统一日志接口
class LoggerService {
  info(message) {
    throw new Error('必须实现info方法');
  }
  
  error(message) {
    throw new Error('必须实现error方法');
  }
}

// Console适配器
class ConsoleLoggerAdapter extends LoggerService {
  info(message) {
    console.log(`[INFO] ${new Date().toISOString()}: ${message}`);
  }

  error(message) {
    console.error(`[ERROR] ${new Date().toISOString()}: ${message}`);
  }
}

// File适配器
class FileLoggerAdapter extends LoggerService {
  constructor(fileSystem) {
    super();
    this.fs = fileSystem;
    this.logFile = 'app.log';
  }

  info(message) {
    const logEntry = `[INFO] ${new Date().toISOString()}: ${message}\n`;
    this.fs.appendFileSync(this.logFile, logEntry);
  }

  error(message) {
    const logEntry = `[ERROR] ${new Date().toISOString()}: ${message}\n`;
    this.fs.appendFileSync(this.logFile, logEntry);
  }
}

// ELK适配器
class ELKLoggerAdapter extends LoggerService {
  constructor(elkClient) {
    super();
    this.client = elkClient;
  }

  async info(message) {
    await this.client.index({
      index: 'app-logs',
      body: {
        level: 'info',
        message,
        timestamp: new Date().toISOString()
      }
    });
  }
}

案例5:缓存服务适配

// 统一缓存接口
class CacheService {
  async get(key) {
    throw new Error('必须实现get方法');
  }

  async set(key, value, ttl) {
    throw new Error('必须实现set方法');
  }
}

// Redis适配器
class RedisCacheAdapter extends CacheService {
  constructor(redisClient) {
    super();
    this.client = redisClient;
  }

  async get(key) {
    const value = await this.client.get(key);
    return value ? JSON.parse(value) : null;
  }

  async set(key, value, ttl = 3600) {
    const serializedValue = JSON.stringify(value);
    await this.client.setex(key, ttl, serializedValue);
  }
}

// Memcached适配器
class MemcachedCacheAdapter extends CacheService {
  constructor(memcachedClient) {
    super();
    this.client = memcachedClient;
  }

  async get(key) {
    return new Promise((resolve, reject) => {
      this.client.get(key, (err, data) => {
        if (err) reject(err);
        else resolve(data ? JSON.parse(data) : null);
      });
    });
  }

  async set(key, value, ttl = 3600) {
    return new Promise((resolve, reject) => {
      const serializedValue = JSON.stringify(value);
      this.client.set(key, serializedValue, ttl, (err) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }
}

// 内存适配器(开发环境使用)
class MemoryCacheAdapter extends CacheService {
  constructor() {
    super();
    this.cache = new Map();
  }

  async get(key) {
    const item = this.cache.get(key);
    if (!item) return null;
    
    // 检查是否过期
    if (item.expiry && item.expiry < Date.now()) {
      this.cache.delete(key);
      return null;
    }
    
    return item.value;
  }

  async set(key, value, ttl) {
    const item = {
      value,
      expiry: ttl ? Date.now() + ttl * 1000 : null
    };
    this.cache.set(key, item);
  }
}

五、最佳实践总结

何时使用适配器模式

  1. 系统需要复用现有类,但接口不符合需求
  2. 需要统一多个类的接口,提供统一的调用方式
  3. 需要兼容老代码,同时引入新功能
  4. 第三方库接口不一致,需要统一处理

适配器模式的优点

  • 解耦客户端和适配者:客户端不需要知道适配者的具体实现
  • 提高代码复用性:现有类可以被多个不同的客户端使用
  • 增强扩展性:很容易添加新的适配器来支持新的服务

适配器模式的缺点

  • 增加系统复杂性:需要维护额外的适配器类
  • 可能影响性能:多了一层调用,但通常可以忽略不计

设计注意事项

  1. 保持适配器轻量:只做必要的接口转换,不要包含业务逻辑
  2. 使用依赖注入:通过构造函数注入适配者,提高可测试性
  3. 提供默认实现:为可选方法提供合理的默认行为
  4. 错误处理统一:确保所有适配器的错误处理方式一致

结语

适配器模式是前端开发中极其重要的设计模式,它让我们的代码在面对不断变化的外部服务时保持稳定和整洁。掌握了这个模式,你就拥有了应对接口差异化的强大武器。

下次当你面对又一个API接口不一致的问题时,不要头疼——这正是适配器模式大显身手的好时机!

记住:好的架构不是没有变化,而是能够优雅地应对变化。