阿里云主机折上折
  • 微信号
Current Site:Index > Adapter pattern (Adapter) interface conversion practice

Adapter pattern (Adapter) interface conversion practice

Author:Chuan Chen 阅读数:3422人阅读 分类: JavaScript

The Core Idea of the Adapter Pattern

The adapter pattern is a structural design pattern that allows incompatible interfaces to collaborate. Just like a power adapter in the real world enables plugs from different countries to work, in programming, an adapter acts as a bridge between two incompatible interfaces. This pattern is particularly useful when we need to use a class whose interface doesn't match other code.

Implementation of the Adapter Pattern in JavaScript

In JavaScript, there are two main ways to implement the adapter pattern:

  1. Class Adapter: Adapts interfaces through inheritance
  2. Object Adapter: Adapts interfaces through composition

Due to limited support for multiple inheritance in JavaScript, the object adapter is more common. Here's a simple example of an object adapter:

// Target interface
class Target {
  request() {
    return 'Target: Default behavior';
  }
}

// Class to be adapted
class Adaptee {
  specificRequest() {
    return '.eetpadA eht fo roivaheb laicepS';
  }
}

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

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

// Usage
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request()); // Output: Adapter: (Translated) Special behavior of the Adaptee.

Practical Application Scenarios

Third-Party Library Integration

When introducing third-party libraries, their APIs may not match our existing system's interfaces. For example, we have a unified logging system interface:

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

But the new third-party logging library uses a different method signature:

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

An adapter can be created:

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

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

// Usage
const thirdPartyLogger = new ThirdPartyLogger();
const logger = new LoggerAdapter(thirdPartyLogger);
logger.log('The adapter pattern is really useful!'); // Output: [THIRD PARTY] The adapter pattern is really useful!

Data Format Conversion

Another common scenario is data format conversion. Suppose our system expects user data in this format:

{
  fullName: 'Zhang San',
  age: 30
}

But the backend API returns data in this format:

{
  name: 'Zhang San',
  years: 30
}

A user data adapter can be created:

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

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

// Usage
const apiResponse = { name: 'Li Si', years: 25 };
const adaptedUser = new UserAdapter(apiResponse).formattedUser;
console.log(adaptedUser); // Output: { fullName: 'Li Si', age: 25 }

Advanced Adapter Pattern Applications

Multi-Adapter Systems

In complex systems, multiple adapters may be needed to handle different interface variants. For example, adapters for different map APIs:

// Unified map interface
class Map {
  display(lat, lng) {}
}

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

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

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

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

// Usage
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 Adapters

The adapter pattern can also be applied at the function level, especially when dealing with callback functions or event handlers:

// Original function
function oldApi(callback) {
  // Simulate async operation
  setTimeout(() => {
    callback(null, { data: 'Data returned from old API' });
  }, 1000);
}

// New API expects a Promise
function newApi() {
  return new Promise((resolve, reject) => {
    oldApi((err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

// Or create a more generic adapter function
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)); // Output: { data: 'Data returned from old API' }

Pros and Cons of the Adapter Pattern

Advantages

  1. Single Responsibility Principle: Separates interface conversion code from business logic
  2. Open/Closed Principle: Introduces new adapters without modifying existing code
  3. Improved Reusability: Existing classes can be reused in systems with different interfaces
  4. Decoupling: Client code is decoupled from concrete implementations

Disadvantages

  1. Increased Complexity: Additional layers add complexity to the code
  2. Performance Overhead: Indirect calls may introduce slight performance penalties
  3. Overuse: May lead to too many small classes in the system, making maintenance difficult

Relationship with Other Patterns

  1. Decorator Pattern: Adapters change object interfaces, decorators enhance object functionality
  2. Facade Pattern: Adapters make existing interfaces usable, facades define new interfaces
  3. Proxy Pattern: Adapters convert interfaces, proxies control access

Considerations in Real Projects

When implementing adapters, consider the following factors:

  1. Interface Differences: Greater differences may lead to more complex adapters
  2. Performance Impact: Data conversion may become a bottleneck
  3. Maintenance Costs: Adapters need to be kept in sync with the adapted code
  4. Testing Strategy: Adapters require thorough testing to ensure correct conversion

For example, adapting different form libraries in a React application:

// Assume we have two form libraries
class FormikForm {
  getValues() {
    return { /* Formik-formatted data */ };
  }
}

class FinalForm {
  getFormState() {
    return { /* Final Form-formatted data */ };
  }
}

// Create a unified form adapter
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('Unsupported form type');
  }

  transformFormikData(data) {
    // Convert Formik data to unified format
    return { /* Unified format data */ };
  }

  transformFinalFormData(data) {
    // Convert Final Form data to unified format
    return { /* Unified format data */ };
  }
}

// Usage
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();

Browser API Adaptation

In modern web development, handling browser API differences is common, and the adapter pattern is well-suited for this scenario:

// Unified storage interface
class Storage {
  setItem(key, value) {}
  getItem(key) {}
}

// LocalStorage adapter
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 adapter
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);
    });
  }
}

// Usage
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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.