Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Adapter
LLD

Adapter

Learn how to make incompatible interfaces work together without changing their existing implementations.

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 the List interface.
  • 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

PatternIntent difference
AdapterChanges the interface to what clients expect
FacadeSimplifies a subsystem; new higher-level interface
DecoratorSame interface, added behavior
ProxySame 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.

My Private Notes

Notes are auto-saved locally to this device.