Design Patterns

Adapter Design Pattern

I inherited a codebase that used a homegrown logger. The new team wanted Winston. Rewriting every log() call across 200 files wasn't happening. Instead, I...

23 Mar 2024

Adapter Design Pattern

I inherited a codebase that used a homegrown logger. The new team wanted Winston. Rewriting every log() call across 200 files wasn't happening. Instead, I wrapped Winston behind the old interface. Done in an hour.

That's the Adapter pattern. It makes an incompatible interface work where a different interface is expected. You write a thin wrapper — the adapter — and existing code never knows the difference.

Think of it like a power plug converter. Your laptop charger has a US plug. The wall outlet is European. The adapter sits in between. Neither the charger nor the outlet changes.

Javascript
class LegacyLogger {
  log(message) {
    console.log(`Legacy Logger: ${message}`);
  }
}

class Logger {
  log(message) {
    throw new Error("log method must be implemented.");
  }
}

class LegacyLoggerAdapter extends Logger {
  constructor() {
    super();
    this.legacyLogger = new LegacyLogger();
  }

  log(message) {
    this.legacyLogger.log(message);
  }
}

const logger = new LegacyLoggerAdapter();
logger.log("This is a message.");

Logger is the target interface your code expects. LegacyLogger is the existing component with a different (or incompatible) interface. LegacyLoggerAdapter bridges the gap — it implements the target interface and delegates calls to the legacy component.

Client code works with Logger. It never touches LegacyLogger directly.

The benefit: You integrate old or third-party code without modifying it. Your existing code stays untouched. Swapping implementations later is trivial — write a new adapter.

The cost: Every adapter is another layer of indirection. If you're wrapping too many things, you might be papering over a deeper design problem. Adapters also hide the real interface, which can confuse developers who don't know the adapter exists.

Use this pattern when integrating legacy systems, switching third-party libraries, or normalizing inconsistent APIs behind a single interface. If you control both sides and can change the interface directly, just change it — don't add an adapter for the sake of it.

Keep reading