Adapter Design Pattern in C++



Adapter is a part of Structural Design Patterns. Adapter pattern works like a bridge between two incompatible interfaces. It involves a single class which is responsible for joining the functionalities of interfaces that are independent and not compatible with each other. This pattern is also known as Wrapper.

Adapter works as a middleman of two things that do not go along together. For example, your laptop has a USB port and you have a SD card that you want to connect to your laptop. Here, USB and SD are two incompatible interfaces. To make them compatible, you need an adapter that can convert SD to USB. So, in the market you will find SD to USB adapter. This adapter will have SD port on one side and USB port on another side. You can connect your SD card to the adapter and then connect the adapter to your laptop's USB port. Now, you can access your SD card data through your laptop.

The Adapter pattern is used when you want to use some existing code, but its interface does not match the one you need. In such cases, you create an adapter class that implements the interface you want and translates calls to the existing code.

Adapter Design Pattern

In the above image, we have a Phone which does not have headphone jack. So, we cannot connect our headphones directly to the Phone. To solve this problem, we use an Adapter which has a lightning connector on one side and a headphone jack on another side. We can connect the adapter to the Phone and then connect our headphones to the adapter. This way, we can use our headphones with the Phone. This is an example of the Adapter pattern in real life.

Types of Adapter Pattern

There are two types of Adapter pattern

  • Class Adapter Pattern − In this, we use multiple inheritance to adapt one interface to another.
  • Object Adapter Pattern − We use composition to adapt one interface to another.

Let's understand in detail how these two types of adapter patterns work.

Class Adapter Pattern in C++

We use Class Adapter Pattern when we want to adopt one interface to another using multiple inheritance. In this pattern, the adapter class inherits from both the target interface and the adaptee class. This way, the adapter can override methods from the target interface and use methods from the adaptee class to provide the functionality we want.

Steps to implement Class Adapter Pattern are as follows −

Class Adapter Pattern

Example of Class Adapter Pattern in C++

In this example, a modern music player wants us to play MP3 files, but we only have an old cassette player. Using the Class Adapter Pattern, we adapt the old cassette player so it can be used as an MP3 player.

#include <iostream>
using namespace std;

// Target interface
class IMediaPlayer {
   public:
   virtual void playMP3(string filename) = 0;
};

// Adaptee class
class OldCassettePlayer {
   public:
      void playCassette(string tape) {
         cout << "Playing cassette tape: " << tape << endl;
      }
};  

// Adapter class
class CassetteToMP3Adapter : public IMediaPlayer, private OldCassettePlayer {
   public:
      void playMP3(string filename) override {
	  
         // Convert MP3 filename to cassette tape format
         string tape = filename + ".cassette";
        playCassette(tape); // Use the adaptee's method
      }
};
// Client code
int main() {
   IMediaPlayer* player = new CassetteToMP3Adapter();
   player->playMP3("MyFavoriteSong");
   delete player;
   return 0;
}

Following is the output of the above code −

Playing cassette tape: MyFavoriteSong.cassette

In this example, the IMediaPlayer interface is the target interface that expects an MP3 player. The OldCassettePlayer class is the adaptee that can only play cassette tapes. The CassetteToMP3Adapter class inherits from both the target interface and the adaptee class, allowing it to adapt the cassette player to work as an MP3 player.

Object Adapter Pattern in C++

We use Object Adapter Pattern when we want to adopt one interface to another using composition. In this pattern, the adapter class contains an instance of the adaptee class and implements the target interface. The adapter class translates calls from the target interface to the adaptee class.

Steps to implement Object Adapter Pattern are as follows:

Steps are almost same as Class Adapter Pattern except that in Object Adapter Pattern we use composition (has-a relationship) instead of inheritance (is-a relationship).

Object Adapter Pattern

Example of Object Adapter Pattern in C++

Now, Let's implement the same example of music player using Object Adapter Pattern.

#include <iostream>
using namespace std;
// Target interface
class IMediaPlayer {
   public:
      virtual void playMP3(string filename) = 0;
};
// Adaptee class
class OldCassettePlayer {
   public:
      void playCassette(string tape) {
         cout << "Playing cassette tape: " << tape << endl;
      }
};
// Adapter class
class CassetteToMP3Adapter : public IMediaPlayer {
   private:
      OldCassettePlayer* cassettePlayer; // Composition
   public:
      CassetteToMP3Adapter(OldCassettePlayer* player) {
         this->cassettePlayer = player;
      }
      void playMP3(string filename) override {
         // Convert MP3 filename to cassette tape format
         string tape = filename + ".cassette";
         cassettePlayer->playCassette(tape); // Use the adaptee's method
      }
};

// Client code
int main() {
   OldCassettePlayer* oldPlayer = new OldCassettePlayer();
   IMediaPlayer* player = new CassetteToMP3Adapter(oldPlayer);
   player->playMP3("MyFavoriteSong");
   delete player;
   delete oldPlayer;
   return 0;
}

Following is the output of the above code −

Playing cassette tape: MyFavoriteSong.cassette

In this example, the IMediaPlayer interface is the target interface that expects an MP3 player. The OldCassettePlayer class is the adaptee that can only play cassette tapes. The CassetteToMP3Adapter class implements the target interface and contains an instance of the adaptee class, which allows it to adapt the cassette player to work as an MP3 player.

When to use Adapter Pattern?

You should consider using the Adapter pattern in the following scenarios −

  • When you want to use an existing class, but its interface does not match the one you need.
  • To create a reusable class that can work with different interfaces.
  • While integrating third-party libraries or legacy code that have incompatible interfaces.
  • Decoupling the client code from the implementation details of the adaptee class.
  • When you want to provide a standard interface for a set of classes with different interfaces.

Real World Applications of Adapter Pattern

Some real-world applications in software development where the Adapter pattern is commonly used are shown in the below image −

Uses Cases of Adapter Pattern

Conclusion

This chapter was about the Adapter design pattern. It’s a structural pattern that helps in making incompatible things work together. We looked at both the Class Adapter and Object Adapter with C++ examples. The Adapter pattern is very useful when working with old code or third-party libraries that don’t match our interface. In short, it makes things work together that normally wouldn’t.

Advertisements