Symptom

Two classes do effectively the same job but expose different method names, argument orders, or unit conventions. Consumers either learn both interfaces or sprinkle ad-hoc conversion code at every call site; converting the unit (cents vs dollars, snake_case vs camelCase, ms vs seconds) happens over and over in inconsistent ways.

Goal

One wrapper class adapts the legacy or third-party interface to the canonical one the rest of the system uses. Consumers see only the canonical interface; the wrapper owns the unit conversion and naming translation in one place.

Before the pattern

class LegacyStripeClient {
charge_card(amount_cents, card_token) {
return fetch('https://stripe.legacy/charge', { method: 'POST', body: `${amount_cents}|${card_token}` });
}
}
// The rest of the app expects: processor.charge({ amount, source })
// Adapters are sprinkled inline at every consumer:
function checkout(cart) {
const client = new LegacyStripeClient();
return client.charge_card(Math.round(cart.total * 100), cart.token);
}
function refundOrder(order) {
const client = new LegacyStripeClient();
return client.charge_card(-Math.round(order.amount * 100), order.token);
}

After the pattern

class StripeAdapter {
constructor(legacy) {
this.legacy = legacy;
}
charge({ amount, source }) {
return this.legacy.charge_card(Math.round(amount * 100), source);
}
}
const processor = new StripeAdapter(new LegacyStripeClient());
function checkout(cart) {
return processor.charge({ amount: cart.total, source: cart.token });
}
function refundOrder(order) {
return processor.charge({ amount: -order.amount, source: order.token });
}
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 4. The book's running example is a graphic toolkit adapting Shape to TextView; this JavaScript adaptation uses a payment-gateway integration where a legacy snake_case + cents API gets adapted to a modern camelCase + dollars interface.
Pressure

Inline conversion at every call site is Alternative Classes with Different Interfaces in its worst form: the smell is duplicated across consumers, and any unit change (cents to mils, dollars to euros) requires editing every site in lockstep with no compiler help.

Tradeoff

The adapter adds an indirection layer; debugging requires stepping through both the adapter and the underlying client. Some adapter implementations leak the underlying interface (return values that still reference the legacy shape), defeating the encapsulation. Adapters that grow optional methods drift toward Middle Man.

Relief

Conversion happens in one place; call sites read in domain terms; replacing the underlying client is one-file work. The unit and naming convention of the canonical interface becomes a contract the type system can enforce.

Trap

When the adapter starts adding behaviour beyond translation — caching, retry logic, metric instrumentation — it stops being an Adapter and becomes a Facade or a Decorator. Honest naming matters: an 'adapter' that does five things is a Service in disguise; clarity demands renaming or decomposition.