CMD Guide
HomeOO & Low-Level DesignStructural

Adapter Pattern

What the Adapter Pattern actually is

The Adapter pattern makes an existing class usable through a different interface without modifying the existing class. You wrap the existing implementation — the adaptee — inside a new class — the adapter — that implements the interface the client expects. The client calls methods on the adapter as if it were a normal implementation of the target interface; the adapter translates those calls into whatever the adaptee already understands.

The classic motivation is integration: a third-party library returns XML, a legacy subsystem speaks a proprietary protocol, or an external service exposes a shape your code was not written for. You do not own the adaptee, so you cannot rewrite it; you can only put a shim in front of it.

Object adapter structure
Object adapter structure

Structure: object adapter versus class adapter

Every adapter has four roles:

There are two ways to build that bridge.

Object adapter (composition)

The adapter implements Target and holds a reference to an Adaptee instance. This is the default form: it works in any object-oriented language, it decouples the adapter from the adaptee's exact class, and it keeps the adaptee's real interface hidden from the client. Because the relationship is composition, one adapter can even delegate to several adaptees if necessary.

Class adapter (inheritance)

The adapter extends Adaptee and implements Target. It reuses the adaptee's implementation through inheritance rather than delegation. This only works in languages that support multiple inheritance of implementation (notably C++); in Java it burns the single superclass slot; in Go it is impossible because Go has no class inheritance at all. For that reason the class adapter is rare in modern code.

Worked example: wrapping a legacy XML parser

Suppose your application already depends on a JsonParser interface. A new requirement forces you to read data from a legacy subsystem that only exposes LegacyXmlParser.parseXml(String). You cannot change LegacyXmlParser; it is owned by another team and shipped as a binary. The solution is an object adapter that speaks JSON on the outside and calls XML on the inside.

Object adapter in Java

XmlToJsonAdapter implements the target interface and delegates to the injected LegacyXmlParser. The conversion detail lives in one place, so the rest of the application keeps using JsonParser as usual.

java
// Target interface that the client expects
interface JsonParser {
    JsonObject parse(String input);
}

// Placeholder result types
class JsonObject {
    @Override public String toString() { return "{}"; }
}

class XmlDocument {
    @Override public String toString() { return "<xml/>"; }
}

// Adaptee: existing, unchangeable class with the useful behavior
class LegacyXmlParser {
    public XmlDocument parseXml(String xml) {
        System.out.println("LegacyXmlParser parsing XML: " + xml);
        return new XmlDocument();
    }
}

// Object adapter: implements Target and composes an Adaptee
class XmlToJsonAdapter implements JsonParser {
    private final LegacyXmlParser xmlParser;

    public XmlToJsonAdapter(LegacyXmlParser xmlParser) {
        this.xmlParser = xmlParser;
    }

    @Override
    public JsonObject parse(String input) {
        XmlDocument xml = xmlParser.parseXml(input);
        return convert(xml);
    }

    private JsonObject convert(XmlDocument xml) {
        // Real conversion omitted for clarity
        System.out.println("Converting " + xml + " to JSON");
        return new JsonObject();
    }
}

// Client: depends only on JsonParser
class ReportingClient {
    private final JsonParser parser;

    public ReportingClient(JsonParser parser) {
        this.parser = parser;
    }

    public void load(String raw) {
        JsonObject result = parser.parse(raw);
        System.out.println("Client received: " + result);
    }
}

public class Solution {
    public static void main(String[] args) {
        LegacyXmlParser legacy = new LegacyXmlParser();
        JsonParser adapter = new XmlToJsonAdapter(legacy);
        ReportingClient client = new ReportingClient(adapter);

        client.load("<report></report>");
    }
}

Object adapter in Go

Go has no classes, so the adapter is a struct that implements the target interface by holding a pointer to the adaptee in a named field. (This is composition via a named field — not Go embedding, which would splice the adaptee's method set into the adapter and re-expose it.) The structural idea is identical to Java: the adapter holds the adaptee and translates the call.

go
package main

import "fmt"

// Target interface
type JsonParser interface {
    Parse(input string) JsonObject
}

type JsonObject struct{}

func (j JsonObject) String() string { return "{}" }

type XmlDocument struct{}

func (x XmlDocument) String() string { return "<xml/>" }

// Adaptee
type LegacyXmlParser struct{}

func (l *LegacyXmlParser) ParseXml(xml string) XmlDocument {
    fmt.Println("LegacyXmlParser parsing XML:", xml)
    return XmlDocument{}
}

// Object adapter
type XmlToJsonAdapter struct {
    xmlParser *LegacyXmlParser
}

func NewXmlToJsonAdapter(xmlParser *LegacyXmlParser) *XmlToJsonAdapter {
    return &XmlToJsonAdapter{xmlParser: xmlParser}
}

func (a *XmlToJsonAdapter) Parse(input string) JsonObject {
    xml := a.xmlParser.ParseXml(input)
    return convert(xml)
}

func convert(xml XmlDocument) JsonObject {
    fmt.Println("Converting", xml, "to JSON")
    return JsonObject{}
}

// Client
type ReportingClient struct {
    parser JsonParser
}

func (c *ReportingClient) Load(raw string) {
    result := c.parser.Parse(raw)
    fmt.Println("Client received:", result)
}

func main() {
    legacy := &LegacyXmlParser{}
    var parser JsonParser = NewXmlToJsonAdapter(legacy)
    client := &ReportingClient{parser: parser}

    client.Load("<report></report>")
}

Class adapter in Java (rare)

The class adapter inherits from the adaptee and implements the target in one class. In Java this is usually a poor trade: it consumes the one allowed superclass, tightly couples the adapter to LegacyXmlParser, and prevents the adapter from wrapping subclasses or multiple sources. It is shown here only for completeness.

java
// Class adapter: rare in Java because it burns the superclass slot
class XmlToJsonClassAdapter extends LegacyXmlParser implements JsonParser {
    @Override
    public JsonObject parse(String input) {
        XmlDocument xml = parseXml(input);  // inherited from LegacyXmlParser
        return convert(xml);
    }

    private JsonObject convert(XmlDocument xml) {
        System.out.println("Converting " + xml + " to JSON");
        return new JsonObject();
    }
}

// Usage: client sees JsonParser, but the object is also a LegacyXmlParser
// JsonParser parser = new XmlToJsonClassAdapter();

When to use it — and when not

Use it when the useful implementation is outside your control and cannot be changed: legacy code, a third-party library, a remote service with a published contract. The adapter localizes the mismatch so the rest of your code stays clean.

Do not use it as a permanent fix for a boundary you own. If the adaptee is internal code, refactor the interface rather than papering over it with an adapter. Also avoid it when a straightforward port or rewrite of the adaptee is cheaper than maintaining a translation layer.

Adapter vs. Facade vs. Bridge

These three patterns all sit between a client and some other code, but they solve different problems.

PatternWhat it doesInterface shape
AdapterMakes one existing interface usable through a different, expected interface.Target interface is dictated by the client; the adaptee's interface is fixed and usually external.
FacadeProvides a simplified front door to an entire subsystem.Facade interface is new and narrower; it hides multiple classes, not just one.
BridgeSplits an abstraction from its implementation so both can vary independently.Both sides are designed together up front; neither is a legacy surface you are stuck with.

Choose Adapter when the incompatibility is accidental and one side is immovable. Choose Facade when the goal is convenience over a messy subsystem. Choose Bridge when you are designing for variability from the start.

Trade-offs

ProsCons
Integrates the unchangeable: lets you use legacy or third-party code without modifying it.Extra layer: adds a class and a forwarding step that must be understood and tested.
Client stays clean: the caller depends on the target interface, not the adaptee.Semantic mismatch: mapping one API to another can hide behavior, exceptions, or performance characteristics.
Object adapters compose: the adapter can wrap any adaptee subtype or even several collaborators.Temptation to postpone refactoring: adapters make it too easy to leave owned technical debt in place.

Takeaways


Sources: Gamma, Helm, Johnson & Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four), "Adapter" chapter; Joshua Bloch, Effective Java, Item 18 — favor composition over inheritance. Re-authored/deepened for this guide; replaces the prior shallow extracted lesson.

The leaky-adapter smell — and translating the whole contract

A correct adapter converts the entire contract, not just the method signature. Consider adapting LegacyBank.pay(cents: int, account: String) to a PaymentGateway.charge(Money) target: the adapter must map Money → cents (unit conversion), source the account from context, and translate the adaptee's error model into the target's — a LegacyBankException the client never heard of must become the PaymentGateway failure the client is written to catch. An adapter that forwards the method but leaks the adaptee's exceptions, units, or nullability is only half an adapter.

The concrete fingerprint that you got it wrong: the client still imports the legacy/adaptee package. If the adaptee's types (its exceptions, its data classes) escape through the adapter into the client, the translation layer is leaking and the decoupling you paid a class for is illusory. A clean adapter is the only code that names the adaptee.

🤖 Don't fully get this? Learn it with Claude

Stuck on Adapter Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Adapter Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Adapter Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Adapter Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Adapter Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Adapter Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes