Adapter – Using Incompatible Interfaces

Jan 30, 2020 | , , , | Programming | 0 comments

This is the third post in our Software Design Pattern series. The Design Pattern that we will be looking at today is called the Adapter Pattern. It is a member of the Structural Patterns as defined in the GOF Design Pattern book. The Adapter Pattern allows a Client to use an Interface that is otherwise unavailable. Another name for the Adapter Pattern is a Wrapper.

Pattern Overview

In the Design Patterns book structural patterns are described as having the purpose of dealing with the composition of classes or objects. Specifically, “Structural class patterns use inheritance to compose interfaces or implementations.” And, “Structural object patterns describe ways to compose objects to realize new functionality”. The specific intent given in the GOF book for the Adapter Pattern is to, “Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”, (page 139).

So what does all of this actually mean? Well, the easiest way to think about the Adapter Pattern is to use the illustration of power adapters for different countries. If you have an appliance with a US plug and try to put it in a UK outlet, you won’t be able to plug it in. This is because the US Plug has only two flat prongs and the UK outlet has 3 prongs in different places than the UK outlet will allow. In order to solve this problem you need an adapter that will accept a US plug and that will also fit into a UK outlet. The Software Design Pattern is really not much different.

Adapter Pattern Parts

The following diagram is a UML representation of the major classes found in an implementation of the Adapter Pattern.

adapter pattern class diagram

Client – This is a class that uses a Target Interface and thus cannot use the Adaptee class directly.

Target Interface – This is the interface that the client uses and is incompatible with the Adaptee.

Adaptee – This is the class that the Client can’t use directly because of it’s dependency on the Target interface.

Adapter – This class implements the Target Interface and “adapts” the target to the Adaptee so that the client in using the Target Interface is indirectly able to invoke functionality on the Adaptee class.

Online Shop Example

I think that it is always helpful to see an example that shows an implementation of the pattern being discussed. In this case, we are going to look at a hypothetical Online Shop that needs to retrieve products from a 3rd party API. The Product System API is not directly available to the OnlineShop ProductsList class so an Adapter is used to query the API indirectly. Here is the UML for the example.

adapter pattern example class diagram

Client – In our example the ProductListings is the Client that depends on the Target Interface IProducts to displayProducts.

Target Interface – In our example the target interface is IProducts.

Adaptee – In our example the adaptee is the ProductSystem API that provides the product data that the ProductListings needs to display.

Adapter – Because the ProductListings uses the IProducts interface to display the product data an adapter needs to be built that implements the IProducts Target Interface and invokes the retrieveProducsArray() operation on the ProductSystem. This is the purpose of the ProductsAdapter.

NOTE: the Online Shop class orchestrates the instantiation and use of each of these components to accomplish the retrieval of the Product data from the Product System.

The following is code that I’ve written to demonstrate how the implementation for this might look and hopefully brings clarity to how the Adapter pattern works. I’ve chosen to write this using TypeScript.

Client Code – ProductListings.ts

import {IProducts} from './IProducts';
export class ProductListings {
    private _products: IProducts;
    constructor(someProducts: IProducts){
        this._products = someProducts;
    }
    public handleDisplayProducts(): string[]{
        return this._products.displayProducts();
    }
}

Here we see that the Client contains a private instance variable of the IProducts interface that is initialized when the class is instantiated. It uses this interface to display products (without knowing how the interface implements this) by calling the displayProducts() operation.

Target Interface – IProducts.ts

export interface IProducts {
    displayProducts(): string[];
}

Here we see the displayProducts() operation specified in the IProducts interface.

Adaptee – ProductsSystem.ts

export class ProductSystem {
    private _products: string[];
    constructor(){
        this._products = [
            'Samsung HD TV',
            'Exercise Bike',
            'Lenovo ThinkPad',
            'Broncos Jersey'
        ]
    }
    retrieveProductsArray(): string[] {
        return this._products;
    }
}

Here we see the ProductSystem which returns a string array of the products that it has when the retrieveProductsArray() operation is called. In this case, the Client calls the IProducts.displayProducts() operation and expects to get these products returned but the ProductSystem does not implement the IProducts interface and thus does not conform to the IProducts contract that the ProductsListing depends upon to retrieve the products.

Adapter – ProductsAdapter.ts

import {ProductSystem} from './ProductSystem';
import {IProducts} from './IProducts';
export class ProductsAdapter implements IProducts{
    private _api = new ProductSystem();
    public displayProducts(): string[]{
        return this._api.retrieveProductsArray();
    }
}

Here we see that this Adapter is the only place in the code that understands that when the displayProducts() operation is called by the client that the retrieveProductsArray() operation should be called on the ProductSystem. This class implements the IProducts interface and thus is used by the Client unknowingly to get the data from the ProductSystem via the retrieveProductsArray() operation.

OnlineShop.ts

import {ProductsAdapter} from './ProductsAdapter';
import {ProductListings} from './ProductListings';
(function init(){
    let adapter = new ProductsAdapter();
    let listings = new ProductListings(adapter);
    let productData = listings.handleDisplayProducts();
    let formattedData = formatProductDataForOnlineStore(productData);
    printFormattedData(formattedData);
})();
function formatProductDataForOnlineStore(productData){
    return productData.map(aProduct => {
        return `<span>${aProduct}</span>`;
    });
}
function printFormattedData(formattedProductData){
    formattedProductData.forEach(item =>{
        console.log(item);
    });
}

Now we see all of this brought together. The init() function is called when this file is run and it does the following:

1 – Creates an instance of the Adapter.

2 – Creates an Instance of the Client and passes the adapter to it.

3 – Invokes the handleDisplayProducts() operation on the client which in turn invokes the displayProducts() operation on the adapter.

4 – The returned product data is then formatted for display in HTML markup and is then printed.

The output of this code is as follows:

adapter pattern output

NOTE: I have put the TypeScript code on GitHub at the following repo if you’re interested in getting a copy of it and playing with it yourself. https://github.com/bradnjones/AdapterDesignPatternPost

Class vs. Object versions of the pattern

In our example we implemented an object version of the pattern. There is also a class version of the pattern. The difference is that an object version has an adapter that contains an instance (in our example the _api instance variable) of the class it wraps whereas the class version of the pattern uses multiple polymorphic interfaces to handle the adapting.

Pros

First, your solution is better decoupled such that the client does not have to be changed in order to use the functionality provided by the adaptee. This promotes better reusability and flexibility in your design.

Second, the client class is not further complicated by having to use a different interface. It works with what it currently uses.

Cons

There really aren’t that many cons, but if I’m going to be picky I’d say the following:

First, there is a slight overhead for a request that is forwarded through the adapter rather than directly taking advantage of the interface itself. This is negligible.

Second, it may be necessary for you to use more than one adapter creating a chain of adapters and this would add to the complexity of your solution.

Conclusion

The Adapter Pattern is a very useful pattern that allows you to bridge your code to interfaces that your code would not otherwise be able to take advantage of. It is important to note that the Adapter Pattern is very similar to 3 other patterns in the GOF book, namely the Facade, Proxy and Decorator patterns. These are easily confused. The key is to remember that the Adapter pattern is about making interfaces compatible, whereas the Facade pattern is about hiding complex interactions, The Proxy pattern is about intercepting calls and controlling access and the Decorator pattern is about adding behavior to an object.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *