Adapter Pattern

How it works

Adapter Pattern
How it works
  1. Input arrives
  2. Core component processes
  3. Result produced
  4. 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

Benefit: 80% faster payment provider switching

🗄️ Database Migration

ORM adapters like Hibernate allow applications to work with different databases (MySQL, PostgreSQL, Oracle) through consistent APIs

Benefit: 95% code reuse across database platforms

📡 API Version Management

Twitter's API adapters maintain compatibility between v1.1 and v2 APIs, allowing gradual migration without breaking existing integrations

Benefit: 70% reduction in migration effort

📱 Cross-Platform UI

React Native adapters bridge native iOS/Android components with JavaScript, enabling code sharing across platforms

Benefit: 60% development time savings

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