본문으로 건너뛰기 Complete Guide to C++ Adapter Pattern | Interface Convers...

Complete Guide to C++ Adapter Pattern | Interface Conversion and Compatibility

Complete Guide to C++ Adapter Pattern | Interface Conversion and Compatibility

이 글의 핵심

A complete guide to the Adapter Pattern. Interface conversion, class adapter vs object adapter, and legacy code integration.

originalId: cpp-adapter-pattern

What is the Adapter Pattern? Why is it needed?

Problem Scenario: Incompatible Interfaces

Problem: The interface of an existing library is incompatible with my code.

// The interface expected by my code
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 can VLCPlayer be used as MediaPlayer?

Solution: The Adapter Pattern converts the interface. The Adapter implements the Target interface and internally calls the Adaptee.

// 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;
};
flowchart LR
    client[Client]
    target[Target\nMediaPlayer]
    adapter[Adapter\nVLCAdapter]
    adaptee[Adaptee\nVLCPlayer]
    
    client --> target
    adapter -.implements.-> target
    adapter --> adaptee

Table of Contents

  1. Object Adapter
  2. Class Adapter
  3. Legacy Code Integration
  4. Common Errors and Solutions
  5. Production Patterns
  6. Complete Example: Payment System

1. Object Adapter

Composition-Based Approach

#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

Multiple Inheritance Approach

#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);  // Direct call
    }
};
int main() {
    MediaPlayer* player = new VLCAdapter();
    player->play("movie.vlc");
    delete player;
}

Advantages: No need to store the Adaptee object.
Disadvantages: Multiple inheritance, not possible if Adaptee is final.

3. Legacy Code Integration

Modernizing Old APIs

#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. Common Errors and Solutions

Issue 1: Memory Leaks

Symptom: Memory leaks.
Cause: Use of raw pointers.

// ❌ Incorrect usage
class Adapter {
    Adaptee* adaptee;  // Who deletes this?
};
// ✅ Correct usage
class Adapter {
    std::unique_ptr<Adaptee> adaptee;
};

Issue 2: Bidirectional Adapter

Symptom: Circular dependency.
Cause: Converting A to B and B to A.

// ✅ Solution: Common interface
class CommonInterface {
    virtual void operation() = 0;
};
class AdapterA : public CommonInterface { /* ... */ };
class AdapterB : public CommonInterface { /* ... */ };

5. Production Patterns

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);
}

Summary

ConceptDescription
Adapter PatternConverts interfaces
PurposeIntegrates incompatible interfaces
StructureTarget, Adapter, Adaptee
AdvantagesLegacy integration, OCP compliance, reusability
DisadvantagesIncreased class count, indirect references
Use CasesLegacy integration, third-party libraries, API conversion
The Adapter Pattern is an essential pattern for integrating incompatible interfaces.

FAQ

Q1: When should I use the Adapter Pattern?

A: Use it for legacy code integration, third-party library usage, and interface mismatch resolution.

Q2: Object Adapter vs Class Adapter?

A: Object Adapter uses composition (recommended), while Class Adapter uses multiple inheritance (possible in C++).

Q3: Difference from Decorator?

A: Adapter focuses on interface conversion, while Decorator focuses on adding functionality.

Q4: Difference from Facade?

A: Adapter transforms a single class, while Facade simplifies a subsystem.

Q5: What about performance overhead?

A: One indirect reference, negligible impact.

Q6: Resources for learning the Adapter Pattern?

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, check out Proxy Pattern.

Other articles related to this topic.



Keywords Covered in This Article (Related Search Terms)

This article covers C++, adapter, pattern, wrapper, interface, legacy.