开头钩子:当接口不一致成为开发噩梦
你有没有遇到过这样的场景:项目需要接入第三个支付平台,但每个平台的API接口千差万别;或者系统要支持多种数据库,但每个数据库的查询语法都不相同。每次新增一个服务,都要重写大量业务逻辑,代码变得越来越臃肿难维护。
这就是适配器模式要解决的痛点。今天,我将带你深入理解JS适配器模式,掌握这个让代码保持整洁、扩展性极强的设计模式。
一、适配器模式原理解析
什么是适配器模式?
适配器模式(Adapter Pattern)属于结构型设计模式,它的核心思想是:将一个类的接口转换成客户期望的另一个接口。适配器让那些接口不兼容的类可以一起工作。
适配器的三种角色
- 目标接口(Target):客户端期望的接口
- 适配者(Adaptee):需要被适配的现有接口
- 适配器(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);
}
}
五、最佳实践总结
何时使用适配器模式
- 系统需要复用现有类,但接口不符合需求
- 需要统一多个类的接口,提供统一的调用方式
- 需要兼容老代码,同时引入新功能
- 第三方库接口不一致,需要统一处理
适配器模式的优点
- 解耦客户端和适配者:客户端不需要知道适配者的具体实现
- 提高代码复用性:现有类可以被多个不同的客户端使用
- 增强扩展性:很容易添加新的适配器来支持新的服务
适配器模式的缺点
- 增加系统复杂性:需要维护额外的适配器类
- 可能影响性能:多了一层调用,但通常可以忽略不计
设计注意事项
- 保持适配器轻量:只做必要的接口转换,不要包含业务逻辑
- 使用依赖注入:通过构造函数注入适配者,提高可测试性
- 提供默认实现:为可选方法提供合理的默认行为
- 错误处理统一:确保所有适配器的错误处理方式一致
结语
适配器模式是前端开发中极其重要的设计模式,它让我们的代码在面对不断变化的外部服务时保持稳定和整洁。掌握了这个模式,你就拥有了应对接口差异化的强大武器。
下次当你面对又一个API接口不一致的问题时,不要头疼——这正是适配器模式大显身手的好时机!
记住:好的架构不是没有变化,而是能够优雅地应对变化。