Adapter Pattern - Interface Compatibility in TypeScript
Learn the Adapter pattern for making incompatible interfaces work together. TypeScript examples for legacy integration, API wrappers, and third-party libraries.
Detailed Explanation
Adapter Pattern
The Adapter pattern converts the interface of a class into another interface that clients expect. It allows classes that could not work together due to incompatible interfaces to collaborate.
Two Flavors
- Object Adapter (composition): The adapter holds a reference to the adaptee and delegates calls. This is the preferred approach in most languages.
- Class Adapter (inheritance): The adapter extends both the target interface and the adaptee class. This requires multiple inheritance and is rarely used in TypeScript.
Practical Example
// Old analytics library
class LegacyAnalytics {
trackEvent(category: string, action: string, label: string) {
return { category, action, label };
}
}
// New interface your app expects
interface Analytics {
track(event: { name: string; properties: Record<string, string> }): void;
}
// Adapter
class AnalyticsAdapter implements Analytics {
constructor(private legacy: LegacyAnalytics) {}
track(event: { name: string; properties: Record<string, string> }) {
this.legacy.trackEvent(
event.properties.category || "general",
event.name,
JSON.stringify(event.properties)
);
}
}
Adapter vs Facade
Both simplify interfaces, but for different reasons. An Adapter makes an existing interface match a target interface (same functionality, different shape). A Facade simplifies a complex subsystem by providing a new, simpler interface (reduced functionality, easier to use).
When to Use
Use Adapter when you need to integrate a third-party library that has a different interface from your codebase, when migrating from one API to another gradually, or when creating a consistent interface for multiple implementations that each have their own API.
Use Case
Adapter is essential when integrating third-party payment gateways (Stripe, PayPal) behind a uniform payment interface, wrapping legacy REST APIs to match modern GraphQL resolvers, converting between data formats (XML to JSON adapters), and creating database driver abstractions.