7 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Adapter Pattern
1. What Is It?
The pattern converts the of a class into another that clients expect, allowing classes with incompatible interfaces to collaborate. It acts as a wrapper that translates calls from the client's expected interface into calls the adaptee's interface can handle — exactly like a physical power plug that lets a device with a European plug work in an American socket.
Without Adapter, integrating legacy code, third-party libraries, or any system that doesn't fit your existing interface contracts forces you to either modify the incompatible class (which may not be possible) or litter client code with translation logic. Adapter isolates that translation in one place.
Your team is integrating a third-party payment library whose charge() method signature is completely different from the processPayment() interface your entire codebase already depends on. You cannot modify the third-party library. Which approach best applies the Adapter pattern to solve this?
2. How It Works
Step-by-step mechanics:
- Define (or identify) the
Targetthat the client code expects. - Identify the
Adaptee— the existing class with the incompatible you want to use. - Create an
class that implementsTargetand wraps theAdapteevia . - In each
Targetmethod, translate the call to the appropriateAdapteemethod(s). - Client code only ever interacts with
Target— theAdapteeis hidden inside the.
Two structural variants:
- Object ( — preferred): The Adapter holds a reference to an
Adapteeinstance. Works in all languages. More flexible: can adapt multiple Adaptee subclasses. - Class Adapter (multiple ): The Adapter inherits from both
TargetandAdaptee. Only viable in languages supporting multiple (Python, C++). Not possible in Java for concrete classes.
Python
from __future__ import annotations class RoundHole: """Target — the interface the client works with.""" def __init__(self, radius: float) -> None: self.radius = radius def fits(self, peg: "RoundPeg") -> bool: return peg.get_radius() <= self.radius class RoundPeg: """Target type — fits directly into RoundHole.""" def __init__(self, radius: float) -> None: self._radius = radius def get_radius(self) -> float: return self._radius class SquarePeg: """Adaptee — incompatible interface; client cannot use this directly in RoundHole.""" def __init__(self, width: float) -> None: self._width = width def get_width(self) -> float: return self._width class SquarePegAdapter(RoundPeg): """Object Adapter — wraps SquarePeg to make it compatible with RoundHole. A square peg fits in a round hole if the hole's radius is at least half the square peg's diagonal (width * sqrt(2) / 2). """ def __init__(self, square_peg: SquarePeg) -> None: self._square_peg = square_peg # Do NOT call super().__init__() — we override get_radius() directly. def get_radius(self) -> float: import math return self._square_peg.get_width() * math.sqrt(2) / 2 # Usage hole = RoundHole(radius=5.0) round_peg = RoundPeg(radius=5.0) print(hole.fits(round_peg)) # True small_square_peg = SquarePeg(width=5.0) large_square_peg = SquarePeg(width=10.0) adapter_small = SquarePegAdapter(small_square_peg) adapter_large = SquarePegAdapter(large_square_peg) print(hole.fits(adapter_small)) # True (5 * sqrt(2)/2 ≈ 3.54 <= 5.0) print(hole.fits(adapter_large)) # False (10 * sqrt(2)/2 ≈ 7.07 > 5.0)
# Class Adapter variant (Python — multiple inheritance) class SquarePegClassAdapter(RoundPeg, SquarePeg): def __init__(self, width: float) -> None: SquarePeg.__init__(self, width) def get_radius(self) -> float: import math return self.get_width() * math.sqrt(2) / 2
Java
import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; // ---- Core pattern illustration ---- public interface Target { String request(); } public class Adaptee { public String specificRequest() { return ".eetpadA eht fo roivaheb laicepS"; // reversed string } } // Object Adapter — preferred in Java (no multiple inheritance for classes) public class Adapter implements Target { private final Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public String request() { String reversed = adaptee.specificRequest(); return new StringBuilder(reversed).reverse().toString(); } } public class Client { public static void execute(Target target) { System.out.println(target.request()); } public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target adapter = new Adapter(adaptee); execute(adapter); // "Special behavior of the Adaptee." } } // ---- Real-world Java analog ---- // InputStreamReader adapts InputStream (byte-oriented) to Reader (char-oriented) InputStream byteStream = System.in; Reader charReader = new InputStreamReader(byteStream, StandardCharsets.UTF_8); // Client code works with Reader interface throughout
3. Variants & Comparisons
| Approach | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Object Adapter | Composition: holds Adaptee instance | Works in all languages; can adapt Adaptee subclasses; follows Composition over Inheritance | Slightly more indirection | Default choice — almost always |
| Class Adapter | Multiple inheritance: extends both Target and Adaptee | Can override Adaptee behavior; slightly less indirection | Requires multiple inheritance; inflexible (bound to one Adaptee class) | Python/C++; when overriding Adaptee is needed |
| Two-way Adapter | Implements both Target and Adaptee interfaces | Bidirectional compatibility | Complex; rarely needed | Legacy system bridging |
| Facade (related) | Simplifies a complex subsystem behind one interface | Reduces complexity | Doesn't translate incompatible interfaces | Simplifying, not adapting |
vs. :
- changes the — client uses a different than the adaptee provides.
- keeps the same interface — client sees the same interface with added behavior.
Adapter vs. Proxy:
- Proxy keeps the same interface as the subject and controls access to it.
- provides a different interface than the adaptee.
You are integrating a third-party logging library whose interface is incompatible with your application's logger interface. You decide to use an Adapter. A colleague suggests using a Decorator instead. What is the key reason the Adapter pattern is the right choice here, while Decorator is not?
4. When to Use It (and When NOT To)
Use when:
- You want to use an existing class but its doesn't match what your client code expects.
- You're integrating a third-party library or legacy code that you cannot modify.
- You want to create a reusable class that cooperates with unrelated classes with incompatible interfaces.
Do NOT use when:
- You have full control over both the client and the adaptee — it's cleaner to align the interfaces directly.
- The translation is trivial and would be clearer as inline code.
- You're trying to simplify a complex subsystem — use Facade instead.
Anti-patterns:
- Leaky Adapter — exposing adaptee-specific methods or concepts through the adapter, the client to the adaptee after all.
- Multi-adapter chains — adapting an adapter to another adapter; the translation logic becomes hard to trace. Better to create one direct adapter.
Decision triggers:
- "I want to use class X but it doesn't fit Y" → Adapter
- "I'm integrating a third-party library I can't modify" → Adapter
- "I need to add behavior without changing the interface" → (not Adapter)
Your team is integrating a payment gateway SDK whose API uses a completely different method signature than the PaymentProcessor interface your checkout service depends on. You cannot modify the SDK. Which of the following approaches is most appropriate?
5. Real-World Usage
Java java.io.InputStreamReader
This is the textbook example in the Java standard library. InputStream is byte-oriented (read() returns int bytes); Reader is character-oriented (read() returns decoded characters). InputStreamReader adapts an InputStream to the Reader , handling the charset decoding translation. Client code that works with Reader never needs to know it's sitting on top of a byte stream.
Java Arrays.asList()
Arrays.asList(T... array) returns a List<T> view of a plain Java array. The array doesn't implement List; the returned Arrays$ArrayList wraps the array and translates List method calls (get(), size(), set()) into array operations. Classic object adapter.
Spring HandlerAdapter
Spring MVC's DispatcherServlet uses HandlerAdapter to support multiple controller types (@Controller, HttpRequestHandler, Servlet) through a single handle(request, response, handler) . Each HandlerAdapter implementation adapts a specific controller type to this uniform interface — allowing Spring to invoke any controller type without knowing which kind it is.
Your application processes data from multiple controller types — some are annotated classes, some implement a legacy HttpRequestHandler interface, and some are plain Servlets. You want a single dispatcher component that can invoke any of them without using instanceof checks or branching logic. Which design approach best solves this?
6. Interview Cheat Sheet
Key sentences to demonstrate depth:
- "'s job is purely translation — it makes an incompatible class look like the the client expects, without changing either the client or the adaptee."
- "I default to the Object () because it can adapt Adaptee subclasses and doesn't require multiple — over applies here."
- "Adapter and are easy to confuse: Adapter changes the interface, keeps the same interface and adds behavior. If the client interface stays the same, it's a Decorator."
- "Java's
InputStreamReaderis the clearest real-world example — it bridges the byte-stream world ofInputStreamto the character-stream world ofReaderwithout modifying either."
Common follow-up questions:
- "When would you use a Class Adapter instead of Object Adapter?" → When you need to override Adaptee behavior (not just delegate to it), and you're in Python/C++ where multiple inheritance is available.
- "How is Adapter different from Facade?" → Facade simplifies a complex subsystem behind a new simpler interface; Adapter makes an existing interface compatible with a different expected interface. Facade often creates a new interface; Adapter matches an existing one.
- "What if the adaptee's interface changes?" → Change is isolated to the Adapter class — client code is unaffected. This is the primary benefit of isolating translation in one place.
Connections to other concepts:
- Decorator — same wrapping structure, different intent: Decorator adds behavior on the same interface; Adapter translates to a different interface.
- Facade — simplifies, Adapter translates. Facade reduces complexity; Adapter resolves incompatibility.
- Bridge — separates from implementation for independent variation; Adapter makes existing things work together. Bridge is designed in advance; Adapter is usually applied retrospectively.
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.