📖 What is the Adapter Pattern? (Simple Explanation)

The Adapter Pattern allows two incompatible interfaces to work together.

It translates or maps one object’s structure into another without changing the original object.

Think of it like a plug adapter that allows a U.S. laptop to fit into a European socket!

Keyword: "Adapter = Translator between interfaces."


🎨 Real-World Examples (Easy to Visualize)

🔌 1. Power Plug Adapter

  • Problem:

    U.S. plug won't fit European socket.

  • Adapter:

    A power plug adapter converts the physical interface (plug shape).


🖥️ 2. Phone Charger

  • Problem:

    Your old phone uses a Micro-USB cable, but your new charger is USB-C.

  • Adapter:

    A USB-C to Micro-USB adapter lets you keep using your old cable.


🌍 3. Language Interpreter

  • Problem:

    Two people speak different languages (e.g., English vs. Japanese).

  • Adapter:

    A translator (interpreter) listens to one language and speaks the other.


🖥️ TypeScript Code Example — Payment Gateway Adapter

Scenario:

  • Your app expects a standard Payment interface.
  • But you need to integrate a third-party StripePaymentService that has a different method format.

👉 We’ll build an Adapter to make them compatible without changing the third-party code!


1. Define Your Expected Interface

interface Payment {
  pay(amount: number): void;
}

2. Third-Party (Incompatible) Class

// Provided by Stripe (cannot modify)
class StripePaymentService {
  makePayment(paymentAmount: number) {
    console.log(`Paid $${paymentAmount} using Stripe`);
  }
}

3. Create the Adapter

class StripeAdapter implements Payment {
  private stripeService: StripePaymentService;

  constructor(stripeService: StripePaymentService) {
    this.stripeService = stripeService;
  }

  pay(amount: number) {
    // Translate your method to Stripe's format
    this.stripeService.makePayment(amount);
  }
}

4. Use It Like a Normal Payment

const stripe = new StripePaymentService();
const payment: Payment = new StripeAdapter(stripe);

payment.pay(100);  // Paid $100 using Stripe

✅ Now your app uses pay() consistently without worrying about Stripe’s odd method names.


🎯 Why Use Adapter Pattern?

✅ Keep your app code consistent and clean, even when integrating messy or foreign systems.

✅ Avoid rewriting or touching external/third-party code.

✅ Makes adding or switching external services easier and safer.

✅ Follows Open/Closed Principle (open for extension, closed for modification).


✅ Real-World Practical Uses of Adapter

Real-world example Adapter usage
Integrating Stripe/PayPal APIs Wrap third-party payment SDKs
Database Migrations Adapt old data models to new services
External APIs Normalize different REST or GraphQL APIs
UI Components Adapt legacy props to new component formats
File I/O Adapt XML parsers to JSON interfaces

📌 Visual Diagram

Your System needs: Payment.pay()

           ↓ Adapter
           ↓
Third-party System: Stripe.makePayment()

➡️ Adapter translates pay() into makePayment() behind the scenes.


📦 Key Pattern Structure (Quick Summary)

// Target interface
interface Target {
  request(): void;
}

// Adaptee
class Adaptee {
  specificRequest() { }
}

// Adapter
class Adapter implements Target {
  constructor(private adaptee: Adaptee) {}

  request() {
    this.adaptee.specificRequest();
  }
}

🚀 Important Tip for Senior Level Usage

In bigger systems, Adapter + Dependency Injection (DI) is the cleanest way to inject different external systems without leaking them into your business logic.

✅ Loose coupling

✅ Easy to swap providers

✅ Easy to mock in tests


🌟 Final Summary in One Line

"Adapter pattern lets two systems talk to each other without changing either one."