📖 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."