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.
`instanceof` branches at every consumer site the agent must verify match consistently. Per-consumer translation logic is duplicated; the agent's per-edit cost on a translation-rule change is proportional to consumer count.
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.
One adapter file the agent verifies once; consumers are uniform calls against a single interface. Translation rules are centralized; consumer-side code is generic over the adapted type.
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');
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.
Per-consumer translation defeats static typing — the consumer's interface contract is implicit in the branches rather than declared. Adding a third variant means another branch at every consumer site (Shotgun Surgery scales with consumer × variant count).
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.
Adapter spreads the boundary across one extra class; the agent must trace from consumer through adapter to vendor to understand a single call's full path. Vendor-API changes ripple to the adapter — usually the right place — but the agent must remember to re-verify the adapter when the vendor updates.
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.
Consumers reach for the canonical interface; a new vendor variant is one new adapter file, and existing consumer code does not change because the adapter's signature matches what the consumers already call.
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.
An adapter that smuggles non-trivial logic (validation, error remapping, retries) hides behaviour the agent might expect to see at the consumer or at the vendor — neither location is now authoritative. When the adapter is doing more than shape-conversion, its name should reflect that (Gateway, Anti-Corruption Layer).