Adapter: Interface Translation
The Problem It Solves
Your domain speaks PaymentProcessor.process(PaymentRequest). The vendor SDK you must integrate speaks StripeClient.charge(StripeChargeDTO, ApiKey, Callback). Rewriting the vendor is impossible; changing your domain to match a third-party API shape couples every caller to that vendor forever — and the next vendor swap becomes a migration. Adapter sits between them, translating one interface into another while both sides stay untouched.
YOUR CODE ADAPTER VENDOR SDK
┌───────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ calls target │───────►│ StripeAdapter │───────►│ StripeClient │
│ interface │ maps │ implements │ maps │ (unchangeable │
│ PaymentProc. │ params │ PaymentProcessor │ shapes │ external code) │
└───────────────┘ └──────────────────┘ └──────────────────┘
Razorpay arrives → new RazorpayAdapter → zero changes on the left side
Mechanics (Object Adapter — the standard form)
// TARGET — your domain's contract:
interface PaymentProcessor {
PaymentResult process(PaymentRequest request);
}
// ADAPTEE — vendor class, cannot be modified:
class StripeClient {
ChargeResponse charge(StripeChargeDTO dto) { ... }
}
// ADAPTER — composition + translation:
class StripeAdapter implements PaymentProcessor {
private final StripeClient client;
StripeAdapter(StripeClient client) { this.client = client; }
@Override public PaymentResult process(PaymentRequest req) {
StripeChargeDTO dto = new StripeChargeDTO( // shape translation
req.amount().inCents(),
req.currency().code(),
req.token());
ChargeResponse resp = client.charge(dto); // delegate
return mapResult(resp); // result translation
}
}
Composition (holding the adaptee) is why it’s called object adapter — works in any language, allows adapting via delegation only.
Class Adapter (why Java mostly can’t)
The classic GoF form extends both target and adaptee (multiple inheritance). Java’s single-extends rule permits it only when the target is an interface: class X extends Adaptee implements Target. Rarely better than object adapter — binds at compile time for no gain.
Two-Way & Pluggable Adapters
- Two-way: adapter implements two targets, bridging two mutually-unaware subsystems — rare, useful for legacy↔modern coexistence.
- Pluggable: abstract adapter supplies default no-op implementations so clients override only needed hooks (AWT’s
MouseAdapter) — an adapter/interface-segregation hybrid.
Real-World Sightings
Arrays.asList(...)— adapts an array to theListinterface.- Spring MVC
HandlerAdapter— invokes handlers of wildly different signatures through one dispatch contract. - Every “SDK client wrapper” class ever written in a serious codebase is this pattern.
Adapter vs Facade vs Decorator vs Proxy
| Pattern | Intent difference |
|---|---|
| Adapter | Changes the interface to what clients expect |
| Facade | Simplifies a subsystem; new higher-level interface |
| Decorator | Same interface, added behavior |
| Proxy | Same interface, controlled access |
Trade-offs
- Gains: vendor independence behind one seam; legacy reuse without modification; test fakes trivially implement the target.
- Costs: one indirection layer per integration; translation bugs (null handling, unit mismatches — cents vs dollars) live here and need dedicated tests.
Interview Framing
- “How do you integrate a flaky third-party SDK?” answered with adapter + retry inside the adapter + fake implementation for tests covers the expected arc.
Premium Content
Unlock Adapter and all premium lessons with a subscription.
From ₹199.99/year — See plans