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.
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 });}
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.
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.
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.
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.