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 C

Step-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 ..> TemperatureSensor

Common 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.