← Back to Home

Adapter Pattern

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise due to incompatible interfaces.

What is it?

The Adapter pattern acts as a bridge between two incompatible interfaces. It's like a universal power adapter that lets you plug your device into any outlet worldwide. In code, it wraps existing classes and makes them compatible with your app's expected interface.

The Problem: API Integration Complexity

Your app needs to process payments, but each provider has a completely different API format. The **Adapter Pattern** organizes this complexity cleanly, while **Direct Integration** scatters it everywhere, creating a maintenance nightmare.

🔍 Why Direct Integration Gets Complex

The Real Problem: Scattered Complexity

✅ With Adapter Pattern:
📁 PaymentService.js - Clean interface only
📁 OrderService.js - Clean interface only
📁 RefundService.js - Clean interface only
📁 ReportingService.js - Clean interface only
📁 StripeAdapter.js - All Stripe complexity here
📁 PayPalAdapter.js - All PayPal complexity here
📁 BitcoinAdapter.js - All Bitcoin complexity here
🎯 Result: Complexity isolated, easy to maintain!
💀 With Direct Integration:
📁 PaymentService.js - Stripe + PayPal + Square + Bitcoin logic
📁 OrderService.js - Stripe + PayPal + Square + Bitcoin logic
📁 RefundService.js - Stripe + PayPal + Square + Bitcoin logic
📁 ReportingService.js - Stripe + PayPal + Square + Bitcoin logic
📁 SubscriptionService.js - Stripe + PayPal + Square + Bitcoin logic
📁 InvoiceService.js - Stripe + PayPal + Square + Bitcoin logic
📁 AnalyticsService.js - Stripe + PayPal + Square + Bitcoin logic
📁 ...and 15+ more files - Same duplicated logic everywhere!
�� Result: Complexity scattered everywhere, maintenance nightmare!

📁 The Scattered Code Problem

Duplicated Logic Everywhere

This is why Direct Integration becomes a maintenance nightmare. The same conversion logic gets duplicated in every file that handles payments:

OrderService.js
Duplicate payment logic
// Must manually handle each provider
if (provider === 'stripe') {
  stripe.charges.create({
    amount: total * 100, // Manual conversion
    currency: currency.toLowerCase()
  });
} else if (provider === 'paypal') {
  paypal.payment.create({
    total: total.toString(), // Different conversion
    currency_code: currency.toUpperCase()
  });
}
// ... duplicate logic for each provider
RefundService.js
Same conversion logic repeated
// Same conversion logic duplicated!
if (provider === 'stripe') {
  stripe.refunds.create({
    charge: chargeId,
    amount: refundAmount * 100 // Convert to cents again
  });
} else if (provider === 'paypal') {
  paypal.refund.create({
    amount: refundAmount.toString() // Convert to string again
  });
}
// ... more duplicate logic
ReportingService.js
Provider-specific formatting everywhere
// Provider formatting scattered everywhere
function formatAmount(amount, provider) {
  if (provider === 'stripe') {
    return amount * 100; // Cents conversion again
  } else if (provider === 'paypal') {
    return amount.toString(); // String conversion again
  } else if (provider === 'square') {
    return { amount: amount * 100, currency: 'USD' }; // Nested object again
  }
  // ... same logic repeated in 15+ files
}
SubscriptionService.js
More duplicate conversion logic
// Yet another place with the same conversions
function processSubscription(amount, provider) {
  if (provider === 'stripe') {
    return stripe.subscriptions.create({
      amount: amount * 100, // Cents conversion duplicated
      currency: 'usd'
    });
  }
  // ... same pattern repeated everywhere
}
💥 The Problem:
  • • Same conversion logic duplicated in 20+ files
  • • Add new provider? Update every single file manually
  • • Bug in conversion logic? Fix it in 20+ places
  • • Different developers implement slightly different logic
  • • High chance of missing updates in some files

Code Example: Clean vs Scattered

// Adapter Pattern - Complexity isolated and organized
interface PaymentProcessor {
  processPayment(amount: number, currency: string): PaymentResult;
}

// All complexity hidden in specific adapters
class StripeAdapter implements PaymentProcessor {
  processPayment(amount: number, currency: string) {
    // Handle Stripe's specific requirements here
    return StripeAPI.charges.create({
      amount: amount * 100,           // Stripe wants cents
      currency: currency.toLowerCase() // Stripe wants lowercase
    });
  }
}

class PayPalAdapter implements PaymentProcessor {
  processPayment(amount: number, currency: string) {
    // Handle PayPal's specific requirements here
    return PayPalAPI.payment.create({
      total: amount.toString(),        // PayPal wants string
      currency_code: currency.toUpperCase() // PayPal wants uppercase
    });
  }
}

// Your business logic stays clean and simple!
class OrderService {
  processPayment(processor: PaymentProcessor, amount: number) {
    return processor.processPayment(amount, "USD"); // Clean interface!
  }
}

// vs Direct Integration - Complexity scattered everywhere
class OrderService {
  processPayment(provider: string, amount: number, currency: string) {
    // Must handle every provider's specific format manually
    if (provider === "stripe") {
      return StripeAPI.charges.create({
        amount: amount * 100,           // Manual conversion
        currency: currency.toLowerCase() // Manual formatting
      });
    } else if (provider === "paypal") {
      return PayPalAPI.payment.create({
        total: amount.toString(),        // Different conversion
        currency_code: currency.toUpperCase() // Different formatting
      });
    } else if (provider === "bitcoin") {
      return BitcoinAPI.transactions.send({
        amount_btc: amount / 45000,     // Yet another conversion
        currency: currency              // Yet another format
      });
    }
    // This same logic gets duplicated in RefundService, ReportingService, 
    // SubscriptionService, InvoiceService, and 15+ other files!
  }
}

// RESULT:
// Adapter: Complexity organized in dedicated classes
// Direct: Same complexity duplicated in 20+ files

Common Uses

  • Integrating third-party APIs with different interfaces
  • Making legacy code work with new systems
  • Unifying multiple data sources or services
  • Creating consistent interfaces for inconsistent libraries

When to Use

  • You need to use existing classes with incompatible interfaces
  • You want to create a unified interface for multiple similar services
  • You're integrating third-party libraries or legacy systems
  • You want to isolate your code from external API changes

Benefits

  • Interface Unification: Make incompatible APIs work with the same interface
  • Complexity Isolation: Keep messy integration details in dedicated adapter classes
  • Easy Extension: Add new providers without changing existing business logic
  • Maintainability: Fix bugs in one place instead of 20+ scattered locations
  • Testability: Mock adapters easily for unit testing