Symptom

Two existing classes do similar work but expose different method names, parameter shapes, or argument orders. Client code that wants to treat them uniformly branches on which one it has; each branch contains its own translation logic; new branches appear as new client sites do their own variant of the same translation.

Goal

An Adapter wraps the off-shape class and exposes the canonical interface. Clients receive whichever conformant instance the composition root produces; no client code knows about the underlying difference.

Before the refactoring

// Two classes do similar work through different shapes; callers branch on which they have.
class Logger {
log(level, message) { /* writes to internal log */ }
}
class ThirdPartyReporter {
report(severityName, text, context) { /* delivers to vendor */ }
}
function emitDiagnostic(emitter, level, message) {
if (emitter instanceof Logger) {
emitter.log(level, message);
} else if (emitter instanceof ThirdPartyReporter) {
emitter.report(level.toUpperCase(), message, {});
} else {
throw new Error('unknown emitter');
}
}

After the refactoring

// One adapter gives the vendor class the internal interface; callers stop branching.
class Logger {
log(level, message) { /* writes to internal log */ }
}
class ThirdPartyReporter {
report(severityName, text, context) { /* delivers to vendor */ }
}
class ReporterAsLogger {
constructor(reporter) {
this.reporter = reporter;
}
log(level, message) {
this.reporter.report(level.toUpperCase(), message, {});
}
}
function emitDiagnostic(logger, level, message) {
logger.log(level, message);
}
// At the composition root:
const adaptedReporter = new ReporterAsLogger(new ThirdPartyReporter());
emitDiagnostic(adaptedReporter, 'error', 'something failed');
Example source: Illustrative example written for this site, faithful to Kerievsky's pattern shape in Refactoring to Patterns (Addison-Wesley, 2004), chapter 6. Distinct from Extract Adapter (#5): that pattern pulls per-variant code from one host class into adapters; this pattern wraps an existing class so it conforms to a different existing interface.
Pressure

Alternative-interface branches at every consumer duplicate the translation rules. Subtle translation bugs (wrong case, missing context argument, swapped order) ship as misrouted diagnostic data; tests for the translation live inside each consumer's tests rather than once where they belong.

Tradeoff

Each adapter is one new class; for two-variant systems with stable interfaces, the adapter may add more friction than the inline conversion did. Wrapping a third-party class in an adapter ties the adapter to that vendor's surface — vendor changes ripple through the adapter; consumers stay insulated.

Relief

Consumers operate against one interface; translation lives in one file; new consumers stop knowing the vendor's shape. Vendor swaps become an adapter change, not a consumer-population edit.

Trap

Adapters that grow large translation logic (multi-step shaping, async/sync bridging, error-model translation) become a hidden host for non-trivial behaviour. When the adapter starts containing business rules, what was Adapter has drifted into Anti-Corruption Layer; rename and refactor accordingly.