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.
Structure: object adapter versus class adapter
Every adapter has four roles:
- Target — the interface the client already knows and depends on.
- Client — code that calls methods through the Target interface.
- Adaptee — the existing class with the useful but incompatible implementation.
- Adapter — the bridge that makes the Adaptee satisfy the Target.
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.
// 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.
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.
// 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.
| Pattern | What it does | Interface shape |
|---|---|---|
| Adapter | Makes 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. |
| Facade | Provides a simplified front door to an entire subsystem. | Facade interface is new and narrower; it hides multiple classes, not just one. |
| Bridge | Splits 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
| Pros | Cons |
|---|---|
| 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
- Adapter converts interfaces, not behavior. It makes an existing class look like the interface the client expects.
- Prefer the object adapter. Composition is the default because it works everywhere and keeps the adapter independent of the adaptee's class hierarchy.
- Class adapters are a corner case. They require multiple inheritance and are impractical in Java and impossible in Go.
- Do not adapter your own code into obsolescence. Use adapters for boundaries you do not own; refactor boundaries you do.
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.
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.
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.
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.
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.