C++ Adapter Pattern: Complete Guide | Interface Wrapping & Legacy Integration

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

  1. Object Adapter
  2. Class adapter
  3. Legacy code integration
  4. Frequently occurring problems and solutions
  5. Production Patterns
  6. 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

conceptDescription
Adapter Patternconvert interface
PurposeIntegration of incompatible interfaces
StructureTarget, Adapter, Adaptee
AdvantagesLegacy Integration, OCP Compliance, Reusability
Disadvantagesclass increment, indirect reference
Use CaseLegacy 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:

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.


  • 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