|
| 1 | +--- |
| 2 | +title: GoF Design Patterns - Adapter |
| 3 | +date: 2026-02-04 |
| 4 | +category: Coding |
| 5 | +description: A practical guide to the Adapter pattern from the GoF collection:concepts, when to use it, a C++ implementation example, implementation notes, and references. |
| 6 | +tags: [Coding, DesignPattern] |
| 7 | +recommended: true |
| 8 | +thumbnail: assets/img/ogp.png |
| 9 | +--- |
| 10 | + |
| 11 | +Hello! I'm Pan-kun. |
| 12 | + |
| 13 | +This post covers the Adapter pattern from the GoF (Gang of Four) design patterns. |
| 14 | +I explain it practically, including a C++ sample, how to implement it, when to use it, pitfalls to watch out for, and a Mermaid diagram for visualization. |
| 15 | + |
| 16 | +## Introduction |
| 17 | + |
| 18 | +The Adapter pattern provides a wrapper that connects incompatible interfaces, allowing an existing class to be adapted to the interface expected by clients. |
| 19 | + |
| 20 | +Benefits: |
| 21 | +- You can adapt existing code or third-party libraries to a new interface without changing their implementation. |
| 22 | + |
| 23 | +Drawbacks: |
| 24 | +- Proliferation of wrappers can scatter design responsibilities and reduce readability and maintainability. |
| 25 | +- The added indirection can also introduce runtime overhead. |
| 26 | + |
| 27 | +## Use cases |
| 28 | + |
| 29 | +Common scenarios where you would create an Adapter include: |
| 30 | + |
| 31 | +- Integrating a legacy API or library into a new application interface. |
| 32 | +- When different modules expose incompatible interfaces but you cannot modify the existing implementation. |
| 33 | +- Creating a thin boundary that makes it easy to swap implementations during testing (though using mocks can be simpler when applicable). |
| 34 | + |
| 35 | +## Structure |
| 36 | + |
| 37 | +An Adapter implements the Target interface and internally invokes the Adaptee's operations while performing any required conversions. |
| 38 | + |
| 39 | +```mermaid |
| 40 | +classDiagram |
| 41 | + class Target { |
| 42 | + <<interface>> |
| 43 | + +request() |
| 44 | + } |
| 45 | + class Adaptee { |
| 46 | + +specificRequest(params) |
| 47 | + } |
| 48 | + class Adapter { |
| 49 | + +Adapter(adaptee) |
| 50 | + +request() |
| 51 | + } |
| 52 | + Target <|.. Adapter |
| 53 | + Adapter o-- Adaptee : adapts |
| 54 | +``` |
| 55 | + |
| 56 | +## C++ implementation example |
| 57 | + |
| 58 | +Below is a concise C++ example. |
| 59 | + |
| 60 | +```cpp |
| 61 | +// Example: the client expects to call Target::request() |
| 62 | +struct Target { |
| 63 | + virtual ~Target() = default; |
| 64 | + virtual void request() = 0; // method the client expects |
| 65 | +}; |
| 66 | + |
| 67 | +// Existing (unchangeable) class |
| 68 | +class Adaptee { |
| 69 | +public: |
| 70 | + void specificRequest(const std::string& params) { |
| 71 | + // Complex call in an existing API |
| 72 | + std::cout << "Adaptee::specificRequest with " << params << std::endl; |
| 73 | + } |
| 74 | +}; |
| 75 | + |
| 76 | +// Adapter: implements Target while adapting Adaptee |
| 77 | +class Adapter : public Target { |
| 78 | +public: |
| 79 | + explicit Adapter(std::shared_ptr<Adaptee> adaptee) |
| 80 | + : adaptee_(std::move(adaptee)) {} |
| 81 | + |
| 82 | + void request() override { |
| 83 | + // Perform necessary translation before calling adaptee |
| 84 | + std::string translated = translate(); |
| 85 | + adaptee_->specificRequest(translated); |
| 86 | + } |
| 87 | + |
| 88 | +private: |
| 89 | + std::string translate() { |
| 90 | + // Actual translation logic; simplified here. |
| 91 | + return "translated-params"; |
| 92 | + } |
| 93 | + |
| 94 | + std::shared_ptr<Adaptee> adaptee_; |
| 95 | +}; |
| 96 | + |
| 97 | +// main.cpp usage |
| 98 | +auto adaptee = std::make_shared<Adaptee>(); |
| 99 | +std::unique_ptr<Target> target = std::make_unique<Adapter>(adaptee); |
| 100 | +target->request(); // the client only knows Target |
| 101 | +``` |
| 102 | + |
| 103 | +Key points: |
| 104 | +- Perform data-format conversion, error handling, and method mapping as needed. |
| 105 | +- There are inheritance-based and delegation-based adapters; choose according to your use case. |
| 106 | + In short: inheritance-based adapters leverage class inheritance, while delegation-based adapters implement an interface and forward calls to a contained object. |
| 107 | + |
| 108 | +## Implementation notes |
| 109 | + |
| 110 | +1. Keep translations simple |
| 111 | + - Avoid loading the Adapter with excessive logic or business rules; that inflates its responsibility. |
| 112 | + - If possible, extract translation logic into a separate module. |
| 113 | + |
| 114 | +2. Make ownership and lifecycle explicit |
| 115 | + - In C++, pointer types determine the relationship between Adapter and Adaptee. |
| 116 | + - Manage ownership clearly to avoid leaks and double frees. |
| 117 | + |
| 118 | +3. Consider performance |
| 119 | + - Heavy translations on hot paths become performance bottlenecks. |
| 120 | + - Consider caching translated results when appropriate. |
| 121 | + |
| 122 | +4. Preserve testability |
| 123 | + - A thin Adapter is easier to unit-test. |
| 124 | + - Make Adaptee mockable where practical. |
| 125 | + |
| 126 | +5. Provide logging and robust error handling |
| 127 | + |
| 128 | +## Common anti-patterns |
| 129 | + |
| 130 | +Typical bad practices when applying this pattern: |
| 131 | + |
| 132 | +- Putting substantial domain logic into the Adapter. |
| 133 | +- Adding Adapter layers indiscriminately across a project, deepening call chains. |
| 134 | +- Ignoring semantics lost during conversion or exceptional cases thrown by the adaptee. |
| 135 | + |
| 136 | +## Practical adoption checklist |
| 137 | + |
| 138 | +- Can you modify the existing code directly? If not, an Adapter is a reasonable choice. |
| 139 | +- Is the translation logic simple? If it's complex, consider an intermediary service or a Facade. |
| 140 | +- Do frequency and performance constraints tolerate the translation cost? If not, evaluate alternative designs. |
| 141 | +- Is there a test strategy? Ensure you can use mocks or integration tests as needed. |
| 142 | + |
| 143 | +## Conclusion |
| 144 | + |
| 145 | +The Adapter is a practical and commonly used pattern for bridging incompatible interfaces. However, careless proliferation of Adapters can complicate design. Make responsibilities explicit and localize translation logic when you use this pattern. |
| 146 | +Pay special attention to ownership, exception and error handling, performance, and testability when implementing an Adapter. |
| 147 | + |
| 148 | +References: |
| 149 | + |
| 150 | +[!CARD](https://sourcemaking.com/design_patterns/adapter) |
| 151 | + |
| 152 | +[!CARD](https://en.wikipedia.org/wiki/Adapter_pattern) |
| 153 | + |
| 154 | +That's it for now — next time I'll cover another GoF pattern. |
| 155 | +The examples in this article are simplified for learning purposes; evaluate them carefully against your requirements before adopting them in production. |
| 156 | + |
| 157 | +Thanks for reading — Pan-kun! |
0 commit comments