SDE Path

Design Patterns

Structural Patterns for Clean Object Graphs

Adapter, Decorator, Composite, Facade, Proxy — compose objects so structure stays flexible as requirements grow.

12 min5 steps · 7 checks
Step 1 of 50/7 checks correct

Adapter makes an incompatible interface fit the one your code already expects. You own an interface; a third-party class does the work but speaks a different vocabulary. Rather than rewrite every call site to match the vendor, you write one thin class that translates between the two.

The trigger is concrete: you have a Logger interface your whole codebase calls, and now you must route logs through a vendor's LegacyLogger whose method names and signatures differ. You cannot edit the vendor class, and you do not want its API leaking into your domain.

// Your interface — what the whole app depends on.
interface Logger {
    void log(String level, String message);
}

// Third-party class you cannot change.
class LegacyLogger {
    void writeEntry(int severity, String text) { /* vendor internals */ }
}

// Adapter: implements YOUR interface, delegates to THEIR class.
class LegacyLoggerAdapter implements Logger {
    private final LegacyLogger legacy;

    LegacyLoggerAdapter(LegacyLogger legacy) { this.legacy = legacy; }

    public void log(String level, String message) {
        int severity = "ERROR".equals(level) ? 3 : 1; // translate vocabulary
        legacy.writeEntry(severity, message);
    }
}

The adapter is the only place that knows the vendor exists. Every caller keeps depending on Logger, so swapping the vendor later means writing one new adapter, not touching the app. The same shape wraps a payment SDK: your PaymentGateway.charge(amount) on the outside, the SDK's idiosyncratic call inside. Intent to remember: Adapter changes an interface without changing behavior.

Quick check

Your code depends on a PaymentGateway interface, but the vendor SDK exposes processTransaction(TxnRequest). What does an Adapter let you do?