适配器模式(Adapter)的接口转换实践
适配器模式的核心思想
适配器模式是一种结构型设计模式,它允许不兼容的接口之间进行协作。就像现实世界中的电源适配器可以让不同国家的插头工作一样,在编程中,适配器充当两个不兼容接口之间的桥梁。这种模式特别有用当我们需要使用某个类,但其接口与其他代码不兼容时。
JavaScript中的适配器实现方式
在JavaScript中实现适配器模式主要有两种方式:
- 类适配器:通过继承来适配接口
- 对象适配器:通过组合来适配接口
由于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返回的数据' }
适配器模式的优缺点
优点
- 单一职责原则:可以将接口转换代码从业务逻辑中分离出来
- 开闭原则:无需修改现有代码就能引入新的适配器
- 提高复用性:现有类可以在不同接口的系统中复用
- 解耦:客户端代码与具体实现解耦
缺点
- 增加复杂性:引入额外层会增加代码复杂度
- 性能开销:间接调用可能带来轻微性能损失
- 过度使用:可能导致系统中有过多小类,难以维护
与其他模式的关系
- 与装饰器模式:适配器改变对象接口,装饰器增强对象功能
- 与外观模式:适配器使已有接口可用,外观定义新接口
- 与代理模式:适配器转换接口,代理控制访问
实际项目中的考量
在实现适配器时,需要考虑以下因素:
- 接口差异程度:差异越大,适配器可能越复杂
- 性能影响:数据转换可能成为瓶颈
- 维护成本:需要维护适配器与被适配代码的同步
- 测试策略:适配器需要充分测试以确保正确转换
例如,在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