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

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.
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
- Event Sourcing Checklist: When It Makes Sense & Common Pitfalls
- Error Boundary React Design Pattern
- Context API React Design Pattern
- Higher-Order Component (HOC) React Design Pattern
- Container Component React Design Pattern
- Best Practices for Structuring Express.js Applications with Prisma Design Pattern