C++ Adapter Pattern: Complete Guide | Interface Wrapping & Legacy Integration
이 글의 핵심
Adapter pattern for C++: wrap legacy and third-party APIs behind a clean target interface—composition, templates, and payment-gateway style examples.
Structural patterns (Adapter, Decorator, Proxy, …) are surveyed in C++ structural patterns #19-2 and the overview #20-2. For a JS angle on wrapping APIs, see JavaScript patterns.
What is Adapter Pattern? why you need it
Problem Scenario: Incompatible Interfaces
Problem: The interface of an existing library is not compatible with my code.
// The interface my code expects
class MediaPlayer {
public:
virtual void play(const std::string& filename) = 0;
};
// Existing library (not compatible)
class VLCPlayer {
public:
void playVLC(const std::string& filename) { /* ... */ }
};
// How to use VLCPlayer as MediaPlayer?
Solution: Adapter Pattern converts interfaces. Adapter implements the Target interface and calls Adaptee internally.
// Adapter
class VLCAdapter : public MediaPlayer {
public:
VLCAdapter(std::unique_ptr<VLCPlayer> player)
: vlc(std::move(player)) {}
void play(const std::string& filename) override {
vlc->playVLC(filename); // interface conversion
}
private:
std::unique_ptr<VLCPlayer> vlc;
};
Key Concept: Adapter Pattern acts as a bridge to bridge incompatible interfaces. This is an essential pattern when integrating legacy systems or using third-party libraries.
index
- Object Adapter
- Class adapter
- Legacy code integration
- Frequently occurring problems and solutions
- Production Patterns
- Complete example: Payment system
1. object adapter
An object adapter wraps an Adaptee using composition. This is the most common and recommended method.
Combination method
#include <iostream>
#include <memory>
#include <string>
class MediaPlayer {
public:
virtual void play(const std::string& filename) = 0;
virtual ~MediaPlayer() = default;
};
class VLCPlayer {
public:
void playVLC(const std::string& filename) {
std::cout << "Playing VLC: " << filename << '\n';
}
};
class MP4Player {
public:
void playMP4(const std::string& filename) {
std::cout << "Playing MP4: " << filename << '\n';
}
};
class VLCAdapter : public MediaPlayer {
public:
VLCAdapter() : vlc(std::make_unique<VLCPlayer>()) {}
void play(const std::string& filename) override {
vlc->playVLC(filename);
}
private:
std::unique_ptr<VLCPlayer> vlc;
};
class MP4Adapter : public MediaPlayer {
public:
MP4Adapter() : mp4(std::make_unique<MP4Player>()) {}
void play(const std::string& filename) override {
mp4->playMP4(filename);
}
private:
std::unique_ptr<MP4Player> mp4;
};
int main() {
std::unique_ptr<MediaPlayer> player;
player = std::make_unique<VLCAdapter>();
player->play("movie.vlc");
player = std::make_unique<MP4Adapter>();
player->play("movie.mp4");
}
2. class adapter
Class adapters are a way to use multiple inheritance. This is possible in C++, but is less flexible than an object adapter.
Multiple inheritance method
#include <iostream>
#include <string>
class MediaPlayer {
public:
virtual void play(const std::string& filename) = 0;
virtual ~MediaPlayer() = default;
};
class VLCPlayer {
public:
void playVLC(const std::string& filename) {
std::cout << "Playing VLC: " << filename << '\n';
}
};
// class adapter (multiple inheritance)
class VLCAdapter : public MediaPlayer, private VLCPlayer {
public:
void play(const std::string& filename) override {
playVLC(filename); // call directly
}
};
int main() {
MediaPlayer* player = new VLCAdapter();
player->play("movie.vlc");
delete player;
}
Advantage: No need to store Adaptee object. Disadvantage: Multiple inheritance, not possible if Adaptee is final.
3. Legacy code integration
The Adapter Pattern shines when it comes to integrating legacy systems into a modern codebase. You can use the new interface without modifying existing code.
Old APIs into modern interfaces
#include <iostream>
#include <string>
#include <memory>
// Legacy API (C style)
class LegacyRectangle {
public:
void draw(int x1, int y1, int x2, int y2) {
std::cout << "Legacy: Rectangle from (" << x1 << "," << y1
<< ") to (" << x2 << "," << y2 << ")\n";
}
};
// modern interface
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() = default;
};
class Rectangle : public Shape {
public:
Rectangle(int x, int y, int w, int h)
: x_(x), y_(y), width_(w), height_(h) {}
void draw() override {
std::cout << "Modern: Rectangle at (" << x_ << "," << y_
<< ") size " << width_ << "x" << height_ << '\n';
}
private:
int x_, y_, width_, height_;
};
// Adapter
class LegacyRectangleAdapter : public Shape {
public:
LegacyRectangleAdapter(int x, int y, int w, int h)
: x_(x), y_(y), width_(w), height_(h),
legacy(std::make_unique<LegacyRectangle>()) {}
void draw() override {
legacy->draw(x_, y_, x_ + width_, y_ + height_);
}
private:
int x_, y_, width_, height_;
std::unique_ptr<LegacyRectangle> legacy;
};
int main() {
std::unique_ptr<Shape> shape1 = std::make_unique<Rectangle>(10, 20, 100, 50);
shape1->draw();
std::unique_ptr<Shape> shape2 = std::make_unique<LegacyRectangleAdapter>(10, 20, 100, 50);
shape2->draw();
}
4. Frequently occurring problems and solutions
Issue 1: Memory leak
Symptom: Memory leak.
Cause: Use of raw pointer.
// ❌ Incorrect use
class Adapter {
Adaptee* adaptee; // who delete?
};
// ✅ Correct use
class Adapter {
std::unique_ptr<Adaptee> adaptee;
};
Issue 2: Bi-directional adapter
Symptom: Cyclodependency.
Cause: Convert A to B and B to A.
// ✅ SOLVED: Common interface
class CommonInterface {
virtual void operation() = 0;
};
class AdapterA : public CommonInterface { /* ... */ };
class AdapterB : public CommonInterface { /* ... */ };
5. production pattern
Pattern 1: Combined with Factory
class MediaPlayerFactory {
public:
static std::unique_ptr<MediaPlayer> create(const std::string& type) {
if (type == "vlc") {
return std::make_unique<VLCAdapter>();
} else if (type == "mp4") {
return std::make_unique<MP4Adapter>();
}
return nullptr;
}
};
auto player = MediaPlayerFactory::create("vlc");
player->play("movie.vlc");
Pattern 2: Template Adapter
template<typename Adaptee>
class GenericAdapter : public MediaPlayer {
public:
GenericAdapter() : adaptee(std::make_unique<Adaptee>()) {}
void play(const std::string& filename) override {
adaptee->playSpecific(filename);
}
private:
std::unique_ptr<Adaptee> adaptee;
};
6. Complete example: payment system
#include <iostream>
#include <memory>
#include <string>
class PaymentProcessor {
public:
virtual bool processPayment(double amount) = 0;
virtual ~PaymentProcessor() = default;
};
// Legacy PayPal API
class PayPalAPI {
public:
bool sendPayment(double dollars) {
std::cout << "PayPal: Processing $" << dollars << '\n';
return true;
}
};
// Legacy Stripe API
class StripeAPI {
public:
bool charge(int cents) {
std::cout << "Stripe: Charging " << cents << " cents\n";
return true;
}
};
// New Square API
class SquareAPI {
public:
bool makePayment(const std::string& amount) {
std::cout << "Square: Payment of " << amount << '\n';
return true;
}
};
// Adapters
class PayPalAdapter : public PaymentProcessor {
public:
PayPalAdapter() : paypal(std::make_unique<PayPalAPI>()) {}
bool processPayment(double amount) override {
return paypal->sendPayment(amount);
}
private:
std::unique_ptr<PayPalAPI> paypal;
};
class StripeAdapter : public PaymentProcessor {
public:
StripeAdapter() : stripe(std::make_unique<StripeAPI>()) {}
bool processPayment(double amount) override {
int cents = static_cast<int>(amount * 100);
return stripe->charge(cents);
}
private:
std::unique_ptr<StripeAPI> stripe;
};
class SquareAdapter : public PaymentProcessor {
public:
SquareAdapter() : square(std::make_unique<SquareAPI>()) {}
bool processPayment(double amount) override {
return square->makePayment("$" + std::to_string(amount));
}
private:
std::unique_ptr<SquareAPI> square;
};
class PaymentService {
public:
PaymentService(std::unique_ptr<PaymentProcessor> processor)
: processor_(std::move(processor)) {}
void checkout(double amount) {
std::cout << "Processing checkout for $" << amount << '\n';
if (processor_->processPayment(amount)) {
std::cout << "Payment successful!\n\n";
} else {
std::cout << "Payment failed!\n\n";
}
}
private:
std::unique_ptr<PaymentProcessor> processor_;
};
int main() {
PaymentService service1(std::make_unique<PayPalAdapter>());
service1.checkout(99.99);
PaymentService service2(std::make_unique<StripeAdapter>());
service2.checkout(49.50);
PaymentService service3(std::make_unique<SquareAdapter>());
service3.checkout(29.99);
}
organize
| concept | Description |
|---|---|
| Adapter Pattern | convert interface |
| Purpose | Integration of incompatible interfaces |
| Structure | Target, Adapter, Adaptee |
| Advantages | Legacy Integration, OCP Compliance, Reusability |
| Disadvantages | class increment, indirect reference |
| Use Case | Legacy integrations, third-party libraries, API conversions |
Adapter Pattern is an essential pattern to unify incompatible interfaces.
FAQ
Q1: When do I use the Adapter Pattern?
A: Used to integrate legacy code, use third-party libraries, and resolve interface inconsistencies.
Q2: Object adapter vs class adapter?
A: Object adapter combination (recommended), Class adapter multiple inheritance (C++ possible).
Q3: What is the difference from Decorator?
A: Adapter focuses on interface conversion, Decorator focuses on feature addition.
Q4: What is the difference from Facade?
A: Adapter focuses on single class transformation, Facade focuses on subsystem simplification.
Q5: What is the performance overhead?
A: 1 indirect reference, negligible.
Q6: What are the Adapter Pattern learning resources?
A:
- “Design Patterns” by Gang of Four
- “Head First Design Patterns” by Freeman & Freeman
- Refactoring Guru: Adapter Pattern
One-Line Summary: The Adapter Pattern allows you to integrate incompatible interfaces. Next, it would be a good idea to read Proxy Pattern.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ Decorator Pattern Complete Guide | Dynamic addition and combination of functions
- Complete Guide to C++ Facade Pattern | Complex subsystems into one simple interface
- Complete Guide to C++ Bridge Pattern | Increasing scalability by separating implementation and abstraction
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented?
Use this checklist to reduce mistakes and improve code quality.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, adapter, pattern, wrapper, interface, legacy, etc.
Related articles
- C++ Decorator Pattern Complete Guide | Dynamic addition and combination of functions
- Complete Guide to C++ Command Pattern | Undo and macro system
- C++ CRTP Complete Guide | Static polymorphism and compile-time optimization
- Complete Guide to C++ Facade Pattern | Complex subsystems into one simple interface
- C++ Factory Pattern Complete Guide | Object creation encapsulation and extensibility