Adapter Pattern
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 97 of 104.
The Adapter pattern allows incompatible interfaces to work together. It acts as a bridge between two classes, converting the interface of one class into an interface that clients expect. This is particularly useful when integrating legacy code or third-party libraries that don't match your system's interface.
Imagine you have an existing class that does what you need, but its interface doesn't match what your code expects. Instead of modifying either side, you create an adapter that wraps the existing class and translates calls:
#include <iostream>
#include <string>
// Existing class with incompatible interface
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// Target interface that client expects
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// Adapter wraps LegacyPrinter and implements Printer interface
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Translate the call
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// Client uses the expected interface
adapter.print("Hello World");
}The adapter holds a reference to the LegacyPrinter and implements the Printer interface. When print() is called, it delegates to printMessage(). The client code works with the Printer interface without knowing about the legacy implementation underneath.
Use Adapter when you want to use an existing class but its interface doesn't match what you need, or when you need to create a reusable class that cooperates with classes having incompatible interfaces.
Challenge
EasyLet's build a Media Player Adapter system that bridges different audio library interfaces. Imagine you're integrating a legacy audio library into a modern media player application—the old library works perfectly, but its interface doesn't match what your player expects. This is exactly where the Adapter pattern shines.
You'll organize your code across four files:
MediaPlayer.h: Define the target interface that your modern media player expects.Create an abstract
MediaPlayerclass with these pure virtual methods:play(const std::string& filename)— plays the specified filestop()— stops playbacksetVolume(int level)— sets volume (0-100)
Include a virtual destructor.
LegacyAudioLib.h: Create the existing "legacy" audio library with an incompatible interface.Your
LegacyAudioLibclass represents an old library that works differently from what your player expects. It should have:loadAudio(const std::string& path)— printsLegacy: Loading [path]startPlayback()— printsLegacy: Playback startedstopPlayback()— printsLegacy: Playback stoppedadjustVolume(double percentage)— takes a decimal (0.0 to 1.0) and printsLegacy: Volume set to [percentage](show the decimal value)
AudioAdapter.h: Build the adapter that bridges the two interfaces.Your
AudioAdapterclass should inherit fromMediaPlayerand hold a reference to aLegacyAudioLibobject. The adapter translates calls from the modern interface to the legacy one:play()should call bothloadAudio()andstartPlayback()on the legacy librarystop()should delegate tostopPlayback()setVolume()receives an integer (0-100) but the legacy library expects a decimal (0.0-1.0), so you'll need to convert it
main.cpp: Demonstrate the adapter in action.Read two inputs:
- A filename to play (string)
- A volume level (integer, 0-100)
Create a
LegacyAudioLibinstance and anAudioAdapterthat wraps it. Then use the adapter through theMediaPlayerinterface (store it as aMediaPlayerpointer) to:- Set the volume to the input level
- Play the input filename
- Stop playback
For example, with inputs song.mp3 and 75:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stoppedWith inputs podcast.wav and 50:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedNotice how the client code works entirely with the MediaPlayer interface—it has no idea that a legacy library is doing the actual work underneath. The adapter handles all the translation, including converting the integer volume to a decimal. This is the essence of the Adapter pattern: making incompatible interfaces work together without modifying either side.
Cheat sheet
The Adapter pattern allows incompatible interfaces to work together by acting as a bridge between two classes. It converts the interface of one class into an interface that clients expect.
Use the Adapter pattern when you want to use an existing class but its interface doesn't match what you need, or when integrating legacy code or third-party libraries.
Structure
The pattern consists of three main components:
- Target Interface: The interface that the client expects
- Adaptee: The existing class with an incompatible interface
- Adapter: Wraps the adaptee and implements the target interface, translating calls between them
Example
#include <iostream>
#include <string>
// Existing class with incompatible interface (Adaptee)
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// Target interface that client expects
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// Adapter wraps LegacyPrinter and implements Printer interface
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Translate the call
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// Client uses the expected interface
adapter.print("Hello World");
}The adapter holds a reference to the adaptee and implements the target interface. When methods are called on the adapter, it delegates to the adaptee's methods, translating the calls as needed. The client code works with the target interface without knowing about the legacy implementation.
Try it yourself
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"
using namespace std;
int main() {
// Read inputs
string filename;
int volumeLevel;
cin >> filename;
cin >> volumeLevel;
// TODO: Create a LegacyAudioLib instance
// TODO: Create an AudioAdapter that wraps the legacy library
// TODO: Store the adapter as a MediaPlayer pointer
// TODO: Use the MediaPlayer interface to:
// 1. Set the volume to the input level
// 2. Play the input filename
// 3. Stop playback
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRAII as a Pattern3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container