Adapter Pattern
How it works
- Input arrives
- Core component processes
- Result produced
- Observe and iterate
Overview
Wrap an object with a new interface expected by clients without changing the underlying component.Learning Objectives
🎯 Interface Compatibility
Master the technique of making incompatible interfaces work together without modifying existing code
🔧 Legacy System Integration
Learn to integrate legacy systems and third-party libraries with modern application architectures
⚡ Wrapper Design Patterns
Understand when and how to use composition over inheritance for interface adaptation
Real-World Examples
💳 Payment Gateway Integration
Stripe adapters allow e-commerce platforms to switch between different payment processors (PayPal, Square, Braintree) using uniform interfaces
🗄️ Database Migration
ORM adapters like Hibernate allow applications to work with different databases (MySQL, PostgreSQL, Oracle) through consistent APIs
📡 API Version Management
Twitter's API adapters maintain compatibility between v1.1 and v2 APIs, allowing gradual migration without breaking existing integrations
📱 Cross-Platform UI
React Native adapters bridge native iOS/Android components with JavaScript, enabling code sharing across platforms
Code example
Adapting a legacy payment gateway to a modern PaymentProcessor interface:// Target interface expected by clients
interface PaymentProcessor { charge(amount: number, currency: string): boolean }
// Legacy component with incompatible API
class LegacyGateway { pay(amountCents: number): boolean { return amountCents > 0 } }
// Adapter wraps the legacy gateway
class LegacyGatewayAdapter implements PaymentProcessor {
constructor(private gw: LegacyGateway) {}
charge(amount: number, currency: string): boolean {
var cents = Math.round(amount * 100);
return this.gw.pay(cents);
}
}
var processor = new LegacyGatewayAdapter(new LegacyGateway());
var ok = processor.charge(19.99, 'USD');
Implementation Notes
- Use composition (wrapping) rather than inheritance to maintain loose coupling
- Implement the target interface that clients expect to use
- Handle data format conversion between the target and adaptee interfaces
- Consider using dependency injection to make adapters easily swappable
- Add proper error handling when adapting between different exception models
Best Practices
- Keep adapters focused on interface translation without adding business logic
- Use clear naming conventions that indicate the adaptation being performed
- Document the mapping between target and adaptee method signatures
- Consider creating abstract base adapters for families of similar adaptations
- Implement proper resource management if the adaptee requires cleanup
Common Pitfalls
- Adding business logic to adapters instead of keeping them purely translational
- Creating adapters when a facade or bridge pattern would be more appropriate
- Not handling edge cases or error conditions properly during adaptation
- Over-adapting by creating unnecessary layers when direct integration is possible
- Forgetting to test adapter behavior with all possible input combinations