Part 07: Adapter Design Pattern

Part 07: Adapter Design Pattern

Adapter design pattern is a structural design pattern. It helps one interface of an object or class to be translated into another interface to make it compatible with the system. It has three participants, adapter, adaptee and the target. The target and the adaptee initially are incompatible in this case so we introduce adapter which is able to work with both, target and adaptee.

The GoF book, Design Patterns: Elements of Reusable Object-Oriented Software defines adapter pattern's intent as:

"Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces."

Let's take a real world example, suppose that you have to make a stock market monitoring app. The app must get data from multiple sources and show the analytics to the user in the form of charts and graphs.

So we have two parts of the application here, the one which gets the data and the other one which converts that data into charts and graphs. The library to convert the data into charts and graphs requires the data to be in JSON format. Since we are getting the data from multiple sources we will get the data in multiple formats. Suppose we are getting the data in CSV format at the moment from the sources. So now the problem is that the library accepts JSON format but our data is in CSV format.

We might think that we should make a single module or class that converts CSV into JSON but we might get data in other formats too, the class will become a monolith and it will be difficult to extend and maintain.

For this problem adapter pattern comes to help us, since the library and the source of data are incompatible. We will make an adapter to convert CSV format into JSON and hence the two interfaces will be compatible with the system and it will work fine. In addition to this if we started getting data into any other format we will just have to add another adapter to make that data source compatible with the system.

So in this case the adaptee will be the library that converts data into charts and graphs, the target will be the sources we get the data from and we will introduce adapter to make the target and adaptee compatible.

Structure

adapter (1).jpg

Implementation

Note: I won't be implementing the logic for getting data from the source, converting it form CSV to JSON and making charts or graphs.

Let's make a dummy CSV data provider(target) which has a method getData that gets the data from the data sources. Here for simplicity I am returning an object which has properties title and data.

// class for csv data provider
class CSVDataProvider {

  // method for geetting data
  getData() {
    return {
      title: 'data in CSV',
      data: 'dataInCSV'
    };
  }
}

Making Charts(adaptee) class and adding methods makeChart and makeGraph they will log the data's title on the console.

// class for making analytics 
class Charts {

  // method for making charts
  makeChart(data) {
    console.log(`chart for ${data.title}`);
  }

  // method for making graphs
  makeGraph(data) {
    console.log(`graph for ${data.title}`);
  }
}

Now let's make an adapter AdapterForCSV that will help the target and adaptee to be compatible with each other.

// an adapter to make data provider and chart maker compatible
class AdapterForCSV {
  constructor(adaptee) {
    this.adaptee = adaptee;
  }

  // method to convert csv into json
  convertCSVtoJSON(data) {
    data.data = 'dataInJSON';
    return data
  }

  // makeChart method which uses convertCSVtoJSON method and make use of adaptee's makeChart method
  makeChart(data) {
    const dataInJSON = this.convertCSVtoJSON(data);
    this.adaptee.makeChart(dataInJSON);
  }

  // makeGraph method which uses convertCSVtoJSON method and make use of adaptee's makeGraph method
  makeGraph(data) {
    const dataInJSON = this.convertCSVtoJSON(data);
    this.adaptee.makeGraph();
  }
}

Clients call operations on an Adapter instance. In turn, the adapter calls adaptee operations that carry out the request. Let's make the objects and run the code.

function run(){
  const chartmaker = new Charts();
  const dataProvider = new CSVDataProvider();
  const csvAdapter = new AdapterForCSV(chartmaker);

  csvAdapter.makeChart(dataProvider.getData());
}

run();

If you want to check the implementation without using classes you can check it out here on my GitHub. give it a star if you like it.

When To Use Adapter Design Pattern

  • When you want to use an existing class and it's interface does not match with the one needed by your system.
  • When you want to co operate between two unrelated and incompatible interfaces.
  • When you need to use several existing sub-classes, but it's impractical to adapt their interface by sub-classing every one.

Advantages

  • You can separate your data conversion logic from your business logic.
  • You can introduce new types of adapters into the program without breaking the existing code.
  • It helps to make the code reusable.
  • Extensibility is easier.

Disadvantages

  • Because you are using a completely different class in your code, it will make the code complex so it is better to use it when code is too complex. If the code is not too complex then there is no need to use it.

That's a wrap guys. I hope you got what adapter pattern is, how and when to implement it.

Follow me on, Twitter, GitHub and LinkedIn .