Adapter
Learning Objectives
Convert an incompatible interface to the one clients expect.
Implement object and class adapters.
Test code against adapters for external libraries.
Prerequisites: Modules 4, 7.
Intuition and Definition
Intent: convert the interface of a class into another interface clients expect, so classes that couldn't work together because of incompatible interfaces can.
Participants: Target (the interface the client uses), Adaptee (existing class with a different interface), Adapter (implements Target by calling Adaptee). An object adapter holds the adaptee (composition); a class adapter inherits from it (multiple inheritance in C++).
Why It Matters
You rarely control third-party APIs. Adapters keep foreign types, units and error conventions at the boundary so the rest of your code speaks one language.
Syntax and Language Notes
// adapting a callback API to a Promise-based interface
function readFileAsync(path: string): Promise<string> {
return new Promise((ok, fail) => legacyRead(path, (err, data) => (err ? fail(err) : ok(data))));
}Complete Example
#include <iostream>
#include <memory>
#include <string>
// Target: what our cluster code expects
class TemperatureSensor {
public:
virtual ~TemperatureSensor() = default;
virtual double celsius() const = 0;
};
// Adaptee: a vendor library with a different interface, unit and error convention
class VendorThermoProbe {
public:
int readRawFahrenheitTimes10(int& out) const { out = 2036; return 0; } // 203.6 F, returns 0 on success
};
// Object adapter (composition)
class ProbeAdapter : public TemperatureSensor {
const VendorThermoProbe& probe_;
public:
explicit ProbeAdapter(const VendorThermoProbe& p) : probe_(p) {}
double celsius() const override {
int raw = 0;
if (probe_.readRawFahrenheitTimes10(raw) != 0) throw std::runtime_error("probe read failed");
return (raw / 10.0 - 32.0) * 5.0 / 9.0;
}
};
// Class adapter (inherits the adaptee privately: "implemented in terms of")
class ProbeClassAdapter : public TemperatureSensor, private VendorThermoProbe {
public:
double celsius() const override {
int raw = 0;
readRawFahrenheitTimes10(raw);
return (raw / 10.0 - 32.0) * 5.0 / 9.0;
}
};
void showCoolan
…coolant 95.3333 C
coolant 95.3333 CStep-by-Step Walkthrough
The client (showCoolant) only knows TemperatureSensor and Celsius.
ProbeAdapter converts the vendor's unit (tenths of °F) and error-code convention (exception) to ours: 203.6 °F = 95.33 °C.
The class adapter achieves the same with private inheritance — useful to override adaptee virtuals, but couples you to one adaptee class.
classDiagram
class TemperatureSensor {
<<interface>>
+celsius() double
}
TemperatureSensor <|.. ProbeAdapter
ProbeAdapter --> VendorThermoProbe : adapts
Client ..> TemperatureSensorCommon Mistakes
Leaking the adaptee (returning vendor types from the adapter).
Adding business logic in adapters — keep them to translation.
One giant adapter for a whole vendor SDK; adapt only what you use.
Trade-offs and Alternatives
Adapter changes an interface; Facade simplifies one; Decorator keeps the interface and adds behaviour; Proxy keeps the interface and controls access. Testing: write contract tests against TemperatureSensor and run them against both the adapter (with the real or recorded vendor library) and fakes.
Real-World Use
Wrapping payment SDKs, cloud storage clients, sensor drivers, legacy C APIs (adapter classes around C handles), std::stack/std::queue (container adapters), and anti-corruption layers in DDD (Module 74).
Practice
Q1. (Easy) Object adapter uses which relationship to the adaptee?
Answer: Composition (it holds a reference/pointer).
Q2. (Medium) Name two things an adapter typically translates besides method names.
Answer: Units/data formats and error conventions (codes ↔ exceptions/results).
Q3. (Advanced) Why do adapters make code more testable?
Answer: The domain depends on your own small interface; tests use fakes, while the adapter alone is tested against the real library (contract/integration tests).
Interview Question
Q1. "Adapter vs Facade vs Decorator vs Proxy?" Answer: All wrap something. Adapter makes an existing interface *match a different expected interface*. Facade *simplifies* a complex subsystem behind a new, smaller interface. Decorator keeps the *same* interface and *adds behaviour* (stackable). Proxy keeps the *same* interface and *controls access* (lazy, remote, permission, caching).
Quick Quiz
Q1. Which adapter variant needs multiple inheritance in C++?
Answer: The class adapter.
Q2. Should adapters contain business rules?
Answer: No — only translation.
Q3. Name a standard-library adapter.
Answer: std::stack, std::queue or std::priority_queue.
Summary
Adapter converts an interface (and units/errors) to the one clients expect.
Prefer object adapters; keep foreign types at the boundary.
Related: Modules 34, 36, 37, 39, 72.