Definition

The Adapter design pattern is a strtuctural design pattern used to make two unrelated and incompatible interfaces or classes work together that could not be achieved otherwise due to mismatched interfaces. It wraps an existing class with a new interface, which is why it is also known as the Wrapper design pattern.

Think of it as a translation layer between components. A good example in real life is when trying to connect a projector to a laptop. The projector might have a VGA plug, where the computer has a different plug (HDMI for instance). And in order to use the projector, we need an adapter that will make the two interfaces compatible by transforming the connection from one format to the other.

Use Cases

Adapters are usually used when some interfaces need to be integrated and work together with existing parts (interfaces, classes, modules, or classes) in the system.

Another use case of the Adapter design pattern is when a client code requires a particular way of requesting data that our system does not support out of the box with the current interface, where creating another interface that will wrap the existing interface to make it compatible with the client code to use.

I have also seen it being used when "refactoring" some legacy components that were untouchable. So new interfaces were rewritten while keeping the old ones like some other parts of the system were using them.

Examples

Unlike the Façade design pattern, which defines a new higher-level interface wrapping a complicated set of interfaces, the Adapter design pattern uses existing incompatible interfaces to make them compatible and work together. Therefore, we should have an existing code to interface.

// the old shopping cart constructor
function Cart() {}

Cart.prototype.calculateTotal = function(items) {
    let total;
    
    // looping through the items and calculate the total price
    ...

    return total;
}

// the new shopping cart constructor
function NewCart(coupon) {
    this.coupon = coupon;
}

NewCart.prototype.calculateTotalAndApplyCoupon = function(items) {
    // calculate the total and apply the coupon on it 
}

We have two constructors namely Cart and NewCart that represent shopping simple shopping carts functionality. The old shopping cart constructor; Cart, calculate the total of the items using calulateTotal. The new introduces a new functionality that allows the cart to have a coupon applied to the total using calculateTotalAndApplyCoupon. Now let's create an adapter that will make these two incompatible interfaces work together.

function CartAdapter(coupon) {
    let cart = new NewCart(coupon);

    function calculateTotal(items) {
        ...
        cart.calculateTotalAndApplyCoupon(items);
        ...
    }

    return {
        calculateTotal: calculateTotal
    }
}

CartAdapter allows us to keep the API functioning without doing any change by adapting the old cart constructor (interface); Cart to the new cart constructor (interface); NewCart.