Adapter Pattern

The Adapter Pattern, also known as the Wrapper Pattern, serves as a bridge between two interfaces or objects. This design pattern falls under the structural pattern category and combines the functionality of two independent interfaces or objects. The pattern is responsible for incorporating independent or incompatible interfaces and objects. In JavaScript, the adapter pattern is commonly used for framework adaptation, parameter adaptation, and data adaptation.

Description

In software development, the design and coding techniques similar to a power adapter are referred to as the adapter pattern. Typically, a client can access the services provided by the target class through its interface. Sometimes, an existing class may meet the functional needs of the client class, but its interface may not be what the client class expects. This could be due to reasons such as mismatched method names between the existing class and the target class. In such cases, the existing interface needs to be transformed into the expected interface for the client class to ensure reusability of the existing class. Without such transformation, the client class cannot leverage the functionality provided by the existing class. The adapter pattern can perform such transformation. In the adapter pattern, a wrapper class can be defined to wrap an object with an incompatible interface. This wrapped class refers to the adapter, and the object it wraps is the adaptee, i.e., the class being adapted. The adapter provides the interface required by the client class, and the adapter's implementation is to convert the client class's request into a call to the corresponding interface of the adaptee. This means that when the client class calls the adapter's method, the adapter class internally calls the adaptee class's method. This process is transparent to the client class, as it does not directly access the adaptee class. Therefore, the adapter allows classes that cannot interact due to incompatible interfaces to work together.

Pattern Structure

  • Target: Target Class
  • Adapter: Adapter Class
  • Adaptee: Adaptee Class
  • Client: Client Class

Advantages

  • Decouples the target class and the adaptee class by introducing an adapter class to reuse the existing adaptee class without modifying the original code.
  • Increases class transparency and reusability by encapsulating specific implementations in the adaptee class, making it transparent to the client class, and enhancing the reusability of the adaptee.
  • Provides excellent flexibility and extensibility. With the use of a configuration file, it is convenient to replace the adapter without modifying the original code. Additionally, new adapter classes can be added without modifying the existing code, fully complying with the open-closed principle.
  • In the class adapter pattern, a single object adapter can adapt multiple different adaptee classes to the same target, meaning one adapter can adapt both the adaptee class and its subclasses to the target interface.
  • In the object adapter pattern, as the adapter class is a subclass of the adaptee class, it can replace some of the adaptee's methods, making the adapter more flexible.

Disadvantages

  • In languages such as Java and C#, which do not support multiple inheritance, the class adapter pattern can only adapt a single adaptee class at a time, and the target abstract class can only be an abstract class, not a concrete class. This has certain limitations, and it cannot adapt both an adaptee class and its subclasses to the target interface.
  • Compared to the class adapter pattern, it is not easy to replace adaptee class methods in the object adapter pattern. If there is a need to replace one or more methods of the adaptee class, a subclass of the adaptee class needs to be created, and the methods of the adaptee class need to be replaced, making the implementation process complex.

Applicability

  • When using an existing object, but its methods or property interface does not meet your requirements.
  • When creating a reusable object that can work with other unrelated or invisible objects (i.e., objects with incompatible interface methods or properties).
  • When wanting to use an existing object, but cannot use prototype inheritance for each object to match its interface, the object adapter can adapt its parent object's interface methods or properties.

Comparison

  • Although the adapter and bridge patterns are similar, the bridge's starting point is different. The purpose of the bridge is to separate the interface portion and the implementation portion so that they can be more easily and relatively independently modified. The adapter, on the other hand, involves changing the interface of an existing object.
  • The decorator pattern enhances the functionality of other objects without changing their interface, making it better in terms of program transparency than the adapter. The result is that the decorator supports recursive composition, which is impossible using only the adapter.
  • The proxy pattern defines a proxy for another object without changing its interface.

Implementation

class Target {
    say(){
        console.log("Target say");
    }
}

class Adaptee {
    say(){
        console.log("Adaptee say");
    }
}

class Adapter extends Target {
    constructor(adaptee) {
        super();
        this.adaptee = adaptee;
    }
    say() {
        // Use this.adaptee to implement the method in target
        console.log("Adaptee to Target say")
    }
}

(function(){
    var adaptee = new Adaptee();
    var adapter=new Adapter(adaptee);
    adapter.say(); // Adaptee to Target say
})();
/**
    An Example
    There was a lightweight framework very similar to jQuery that needed to be adapted to use jQuery. Only the method for getting elements by id needs to be adapted.
    class Target { // This is the original lightweight framework
        getElement(id){
            return document.getElementById(id);
        }
    }

    var adaptee = $; // Adaptee is jQuery here

    class Adapter extends Target {
        constructor(adaptee) {
            super();
            this.adaptee = adaptee;
        }
        getElement(id) {
            return this.adaptee(`#${id}`);
        }
    }
    
    // Previously, an instance of the Target object, target, was used
    // After adaptation, an instance of the Adapter object, adapter, is used and can point to adapter

 */

Question of the Day

https://github.com/WindrunnerMax/EveryDay

References

https://segmentfault.com/a/1190000012436538 https://www.runoob.com/design-pattern/adapter-pattern.html https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/adapter.html