Adapter Pattern

The Adapter design pattern allows incompatible interfaces to work together by acting as a bridge between them.

It converts the interface of one class into another interface that clients expect. In simple terms, think of it as a plug adapter that lets you connect a device with one type of plug to a power outlet with a different type of socket.

Think of it as using an adapter to plug a European electrical device (with a European plug) into a US power outlet (with a US plug). The adapter converts the European plug into a US plug, enabling the device to function correctly.

The Adapter pattern helps achieve interoperability between classes that otherwise wouldn’t be able to work together due to their incompatible interfaces.

Adapter Pattern C++ Example:

Let’s consider a MediaPlayer interface that represents various media players, and a legacy OldMediaPlayer class with an incompatible interface. We’ll create an Adapter class to make the OldMediaPlayer work with the MediaPlayer interface.

Adapter Pattern Example Class Diagram:

             +---------------+
             |   MediaPlayer |
             +---------------+
             | + play()      |
             +-------^-------+
                     |
                     |
          +----------+-----------+
          |                      |
 +---------------+      +------------------+
 |   MP3Player   |      | OldMediaPlayer   |
 +---------------+      +------------------+
 | + play()      |      | + playOld()      |
 +---------------+      +------------------+

                    ^
                    |
            +-------+-------+
            |               |
    +----------------+  +------------------+
    |   MediaPlayerAdapter   |
    +----------------+  +------------------+
    | - oldPlayer: OldMediaPlayer*        |
    | + play()                        |
    | - convertToOldFormat() |
    +----------------+
#include <iostream>
#include <string>

// MediaPlayer interface
class MediaPlayer {
public:
    virtual void play(std::string filename) = 0;
};

// Concrete MediaPlayer implementation: MP3Player
class MP3Player : public MediaPlayer {
public:
    void play(std::string filename) override {
        std::cout << "Playing MP3 file: " << filename << std::endl;
    }
};

// Legacy OldMediaPlayer class with incompatible interface
class OldMediaPlayer {
public:
    void playOld(std::string oldFilename) {
        std::cout << "Playing old media file: " << oldFilename << std::endl;
    }
};

// Adapter class that adapts OldMediaPlayer to the MediaPlayer interface
class MediaPlayerAdapter : public MediaPlayer {
public:
    MediaPlayerAdapter(OldMediaPlayer* oldPlayer)
        : oldPlayer_(oldPlayer) {}

    void play(std::string filename) override {
        // Converting the filename to the old format (if needed)
        std::string oldFilename = convertToOldFormat(filename);
        oldPlayer_->playOld(oldFilename);
    }

private:
    OldMediaPlayer* oldPlayer_;

    // Helper method to convert the filename to old format
    std::string convertToOldFormat(std::string filename) {
        // For simplicity, we just append "_old" to the filename
        return filename + "_old";
    }
};

int main() {
    // Using the MP3Player (compatible with MediaPlayer interface)
    MediaPlayer* mp3Player = new MP3Player();
    mp3Player->play("song.mp3");

    // Using the OldMediaPlayer through the adapter
    OldMediaPlayer* oldPlayer = new OldMediaPlayer();
    MediaPlayerAdapter* adapter = new MediaPlayerAdapter(oldPlayer);
    adapter->play("old_song.mp4");

    delete mp3Player;
    delete adapter;
    delete oldPlayer;

    return 0;
}

Adapter Pattern C++ Explanation:
In this example, we have a MediaPlayer interface with a method play() that takes the filename of the media to play. The MP3Player class is a concrete implementation of the MediaPlayer interface, and it can play MP3 files.

Next, we have the OldMediaPlayer class, which represents a legacy media player with an incompatible interface. It has a method playOld() that takes the old filename format as input.

To make the OldMediaPlayer compatible with the MediaPlayer interface, we create an MediaPlayerAdapter class. This class inherits from the MediaPlayer interface and holds a pointer to the OldMediaPlayer instance. The play() method of the adapter takes the filename in the new format, converts it to the old format using a helper method convertToOldFormat(), and then calls the playOld() method of the OldMediaPlayer to play the media in the old format.

In the main() function, we demonstrate how the Adapter pattern works. We first use the MP3Player (which is compatible with the MediaPlayer interface) to play an MP3 file. Then, we use the OldMediaPlayer through the MediaPlayerAdapter to play an old media file, and the adapter converts the filename to the old format before passing it to the OldMediaPlayer.

Adapter Pattern Summary:
The Adapter pattern allows objects with incompatible interfaces to work together by creating an adapter class that adapts the interface of one class to another. In this example, the MediaPlayerAdapter makes the OldMediaPlayer compatible with the MediaPlayer interface, allowing it to work with other media players seamlessly. The Adapter pattern promotes code reusability and enables the integration of legacy code or third-party libraries into your application with ease.