Adapter Pattern
Adapter Pattern: Making Incompatible Interfaces Compatible
Section titled “Adapter Pattern: Making Incompatible Interfaces Compatible”Now let’s dive into the Adapter Pattern - one of the most commonly used structural design patterns.
Why Adapter Pattern?
Section titled “Why Adapter Pattern?”Imagine you’re traveling to Europe with a US phone charger. The plug doesn’t fit European sockets! You need an adapter to make your US plug work with European sockets. The Adapter Pattern works the same way!
The Adapter Pattern lets incompatible interfaces work together. It acts as a bridge between two incompatible interfaces, allowing them to collaborate without modifying their source code.
What’s the Use of Adapter Pattern?
Section titled “What’s the Use of Adapter Pattern?”The Adapter Pattern is useful when:
- You need to use existing classes - But their interfaces don’t match what you need
- Integrating third-party libraries - Library interface doesn’t match your code
- Legacy code integration - Old code needs to work with new code
- Multiple incompatible interfaces - Need to make them work together
- You want to avoid modifying - Don’t want to change existing code
What Happens If We Don’t Use Adapter Pattern?
Section titled “What Happens If We Don’t Use Adapter Pattern?”Without the Adapter Pattern, you might:
- Modify existing code - Risk breaking working code
- Create wrapper code - Scattered throughout your codebase
- Tight coupling - Code depends on incompatible interfaces
- Code duplication - Similar conversion logic repeated everywhere
- Violate Open/Closed Principle - Need to modify code to integrate new systems
Simple Example: The Media Player
Section titled “Simple Example: The Media Player”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Adapter Pattern works in practice - showing how incompatible interfaces are made compatible:
sequenceDiagram
participant Client
participant MediaPlayer as MediaPlayer Interface
participant Adapter as MediaAdapter
participant Mp4Player
participant VlcPlayer
Client->>MediaPlayer: play("mp4", "movie.mp4")
activate MediaPlayer
MediaPlayer->>Adapter: play("mp4", "movie.mp4")
activate Adapter
Adapter->>Adapter: Check audio_type
Adapter->>Mp4Player: play_mp4("movie.mp4")
activate Mp4Player
Mp4Player-->>Adapter: Playing MP4
deactivate Mp4Player
Adapter-->>MediaPlayer: Success
deactivate Adapter
MediaPlayer-->>Client: Media playing
deactivate MediaPlayer
Note over Client,VlcPlayer: Adapter converts interface<br/>to make it compatible!
Client->>MediaPlayer: play("vlc", "video.vlc")
activate MediaPlayer
MediaPlayer->>Adapter: play("vlc", "video.vlc")
activate Adapter
Adapter->>Adapter: Check audio_type
Adapter->>VlcPlayer: play_vlc("video.vlc")
activate VlcPlayer
VlcPlayer-->>Adapter: Playing VLC
deactivate VlcPlayer
Adapter-->>MediaPlayer: Success
deactivate Adapter
MediaPlayer-->>Client: Media playing
deactivate MediaPlayer
The Problem
Section titled “The Problem”You’re building a media player application. You have an existing Mp3Player that works great, but you need to support Mp4Player and VlcPlayer which have different interfaces. Without Adapter Pattern:
# ❌ Without Adapter Pattern - Incompatible interfaces!
class Mp3Player: """Existing MP3 player - works great!""" def play_mp3(self, filename: str): print(f"🎵 Playing MP3: {filename}")
class Mp4Player: """MP4 player - different interface!""" def play_mp4(self, filename: str): print(f"🎬 Playing MP4: {filename}")
class VlcPlayer: """VLC player - yet another interface!""" def play_vlc(self, filename: str): print(f"📺 Playing VLC: {filename}")
# Problem: Client needs to know about all different interfaces!class AudioPlayer: def play(self, audio_type: str, filename: str): if audio_type == "mp3": player = Mp3Player() player.play_mp3(filename) # Different method name! elif audio_type == "mp4": player = Mp4Player() player.play_mp4(filename) # Different method name! elif audio_type == "vlc": player = VlcPlayer() player.play_vlc(filename) # Different method name! else: raise ValueError(f"Unsupported audio type: {audio_type}")
# Problems:# - Client needs to know about all player types# - Different method names for each player# - Hard to add new players (need to modify AudioPlayer)# - Violates Open/Closed Principle// ❌ Without Adapter Pattern - Incompatible interfaces!
public class Mp3Player { // Existing MP3 player - works great! public void playMp3(String filename) { System.out.println("🎵 Playing MP3: " + filename); }}
public class Mp4Player { // MP4 player - different interface! public void playMp4(String filename) { System.out.println("🎬 Playing MP4: " + filename); }}
public class VlcPlayer { // VLC player - yet another interface! public void playVlc(String filename) { System.out.println("📺 Playing VLC: " + filename); }}
// Problem: Client needs to know about all different interfaces!public class AudioPlayer { public void play(String audioType, String filename) { if ("mp3".equals(audioType)) { Mp3Player player = new Mp3Player(); player.playMp3(filename); // Different method name! } else if ("mp4".equals(audioType)) { Mp4Player player = new Mp4Player(); player.playMp4(filename); // Different method name! } else if ("vlc".equals(audioType)) { VlcPlayer player = new VlcPlayer(); player.playVlc(filename); // Different method name! } else { throw new IllegalArgumentException("Unsupported audio type: " + audioType); } }}
// Problems:// - Client needs to know about all player types// - Different method names for each player// - Hard to add new players (need to modify AudioPlayer)// - Violates Open/Closed PrincipleProblems:
- Client needs to know about all different player interfaces
- Different method names for each player type
- Hard to add new players - need to modify existing code
- Violates Open/Closed Principle
The Solution: Adapter Pattern
Section titled “The Solution: Adapter Pattern”Class Structure
Section titled “Class Structure”classDiagram
class MediaPlayer {
<<interface>>
+play(audio_type, filename) void
}
class Mp3Player {
+play_mp3(filename) void
}
class Mp4Player {
+play_mp4(filename) void
}
class VlcPlayer {
+play_vlc(filename) void
}
class MediaAdapter {
-mp4Player: Mp4Player
-vlcPlayer: VlcPlayer
+play(audio_type, filename) void
}
class AudioPlayer {
-adapter: MediaAdapter
+play(audio_type, filename) void
}
MediaPlayer <|.. Mp3Player : implements
MediaPlayer <|.. MediaAdapter : implements
MediaAdapter --> Mp4Player : adapts
MediaAdapter --> VlcPlayer : adapts
AudioPlayer --> MediaPlayer : uses
note for MediaAdapter "Converts incompatible\ninterfaces to compatible ones"
note for Mp4Player "Incompatible interface"
note for VlcPlayer "Incompatible interface"
from abc import ABC, abstractmethod
# Step 1: Define the target interface (what client expects)class MediaPlayer(ABC): """Target interface - what the client expects"""
@abstractmethod def play(self, audio_type: str, filename: str) -> None: """Play media file""" pass
# Step 2: Existing compatible class (no adapter needed)class Mp3Player(MediaPlayer): """MP3 Player - already compatible with MediaPlayer interface"""
def play(self, audio_type: str, filename: str) -> None: if audio_type == "mp3": self.play_mp3(filename) else: raise ValueError(f"Mp3Player doesn't support {audio_type}")
def play_mp3(self, filename: str) -> None: print(f"🎵 Playing MP3: {filename}")
# Step 3: Incompatible classes (need adapters)class Mp4Player: """MP4 Player - incompatible interface!"""
def play_mp4(self, filename: str) -> None: print(f"🎬 Playing MP4: {filename}")
class VlcPlayer: """VLC Player - incompatible interface!"""
def play_vlc(self, filename: str) -> None: print(f"📺 Playing VLC: {filename}")
# Step 4: Create the Adapterclass MediaAdapter(MediaPlayer): """Adapter - makes incompatible interfaces compatible"""
def __init__(self): self.mp4_player = Mp4Player() self.vlc_player = VlcPlayer()
def play(self, audio_type: str, filename: str) -> None: """Adapt the incompatible interfaces to match MediaPlayer interface""" if audio_type == "mp4": # Convert play() call to play_mp4() call self.mp4_player.play_mp4(filename) elif audio_type == "vlc": # Convert play() call to play_vlc() call self.vlc_player.play_vlc(filename) else: raise ValueError(f"MediaAdapter doesn't support {audio_type}")
# Step 5: Client code - uses unified interfaceclass AudioPlayer: """Client - uses MediaPlayer interface"""
def __init__(self): self.mp3_player = Mp3Player() self.adapter = MediaAdapter()
def play(self, audio_type: str, filename: str) -> None: """Play media - unified interface for all types!""" if audio_type == "mp3": self.mp3_player.play(audio_type, filename) elif audio_type in ["mp4", "vlc"]: # Use adapter for incompatible players self.adapter.play(audio_type, filename) else: raise ValueError(f"Unsupported audio type: {audio_type}")
# Usage - Clean and simple!def main(): player = AudioPlayer()
# All use the same interface! player.play("mp3", "song.mp3") # Direct - no adapter needed player.play("mp4", "movie.mp4") # Uses adapter player.play("vlc", "video.vlc") # Uses adapter
print("\n✅ All players work through unified interface!")
if __name__ == "__main__": main()// Step 1: Define the target interface (what client expects)interface MediaPlayer { // Target interface - what the client expects void play(String audioType, String filename);}
// Step 2: Existing compatible class (no adapter needed)class Mp3Player implements MediaPlayer { // MP3 Player - already compatible with MediaPlayer interface @Override public void play(String audioType, String filename) { if ("mp3".equals(audioType)) { playMp3(filename); } else { throw new IllegalArgumentException("Mp3Player doesn't support " + audioType); } }
public void playMp3(String filename) { System.out.println("🎵 Playing MP3: " + filename); }}
// Step 3: Incompatible classes (need adapters)class Mp4Player { // MP4 Player - incompatible interface! public void playMp4(String filename) { System.out.println("🎬 Playing MP4: " + filename); }}
class VlcPlayer { // VLC Player - incompatible interface! public void playVlc(String filename) { System.out.println("📺 Playing VLC: " + filename); }}
// Step 4: Create the Adapterclass MediaAdapter implements MediaPlayer { // Adapter - makes incompatible interfaces compatible private Mp4Player mp4Player; private VlcPlayer vlcPlayer;
public MediaAdapter() { this.mp4Player = new Mp4Player(); this.vlcPlayer = new VlcPlayer(); }
@Override public void play(String audioType, String filename) { // Adapt the incompatible interfaces to match MediaPlayer interface if ("mp4".equals(audioType)) { // Convert play() call to playMp4() call mp4Player.playMp4(filename); } else if ("vlc".equals(audioType)) { // Convert play() call to playVlc() call vlcPlayer.playVlc(filename); } else { throw new IllegalArgumentException("MediaAdapter doesn't support " + audioType); } }}
// Step 5: Client code - uses unified interfaceclass AudioPlayer { // Client - uses MediaPlayer interface private Mp3Player mp3Player; private MediaAdapter adapter;
public AudioPlayer() { this.mp3Player = new Mp3Player(); this.adapter = new MediaAdapter(); }
public void play(String audioType, String filename) { // Play media - unified interface for all types! if ("mp3".equals(audioType)) { mp3Player.play(audioType, filename); } else if ("mp4".equals(audioType) || "vlc".equals(audioType)) { // Use adapter for incompatible players adapter.play(audioType, filename); } else { throw new IllegalArgumentException("Unsupported audio type: " + audioType); } }}
// Usage - Clean and simple!public class Main { public static void main(String[] args) { AudioPlayer player = new AudioPlayer();
// All use the same interface! player.play("mp3", "song.mp3"); // Direct - no adapter needed player.play("mp4", "movie.mp4"); // Uses adapter player.play("vlc", "video.vlc"); // Uses adapter
System.out.println("\n✅ All players work through unified interface!"); }}Real-World Software Example: Payment Gateway Integration
Section titled “Real-World Software Example: Payment Gateway Integration”Now let’s see a realistic software example - integrating multiple payment gateways with different interfaces into a unified payment system.
The Problem
Section titled “The Problem”You’re building an e-commerce system that needs to support multiple payment gateways (Stripe, PayPal, Square). Each gateway has a different interface. Without Adapter Pattern:
# ❌ Without Adapter Pattern - Incompatible payment interfaces!
class StripePayment: """Stripe payment gateway - incompatible interface!""" def charge_stripe(self, amount: float, card_token: str) -> dict: # Stripe-specific implementation return {"status": "success", "gateway": "stripe", "transaction_id": f"stripe_{amount}"}
class PayPalPayment: """PayPal payment gateway - different interface!""" def process_paypal(self, amount: float, email: str) -> dict: # PayPal-specific implementation return {"status": "success", "gateway": "paypal", "transaction_id": f"paypal_{amount}"}
class SquarePayment: """Square payment gateway - yet another interface!""" def make_payment(self, amount: float, payment_method: str) -> dict: # Square-specific implementation return {"status": "success", "gateway": "square", "transaction_id": f"square_{amount}"}
# Problem: Client needs to handle all different interfaces!class PaymentProcessor: def process_payment(self, gateway: str, amount: float, payment_data: dict) -> dict: if gateway == "stripe": stripe = StripePayment() return stripe.charge_stripe(amount, payment_data["card_token"]) # Different method! elif gateway == "paypal": paypal = PayPalPayment() return paypal.process_paypal(amount, payment_data["email"]) # Different method! elif gateway == "square": square = SquarePayment() return square.make_payment(amount, payment_data["payment_method"]) # Different method! else: raise ValueError(f"Unsupported gateway: {gateway}")
# Problems:# - Client needs to know about all gateway interfaces# - Different method names and parameters for each gateway# - Hard to add new gateways (need to modify PaymentProcessor)# - Payment data format differs for each gateway// ❌ Without Adapter Pattern - Incompatible payment interfaces!
public class StripePayment { // Stripe payment gateway - incompatible interface! public Map<String, Object> chargeStripe(double amount, String cardToken) { // Stripe-specific implementation Map<String, Object> result = new HashMap<>(); result.put("status", "success"); result.put("gateway", "stripe"); result.put("transaction_id", "stripe_" + amount); return result; }}
public class PayPalPayment { // PayPal payment gateway - different interface! public Map<String, Object> processPaypal(double amount, String email) { // PayPal-specific implementation Map<String, Object> result = new HashMap<>(); result.put("status", "success"); result.put("gateway", "paypal"); result.put("transaction_id", "paypal_" + amount); return result; }}
public class SquarePayment { // Square payment gateway - yet another interface! public Map<String, Object> makePayment(double amount, String paymentMethod) { // Square-specific implementation Map<String, Object> result = new HashMap<>(); result.put("status", "success"); result.put("gateway", "square"); result.put("transaction_id", "square_" + amount); return result; }}
// Problem: Client needs to handle all different interfaces!public class PaymentProcessor { public Map<String, Object> processPayment(String gateway, double amount, Map<String, String> paymentData) { if ("stripe".equals(gateway)) { StripePayment stripe = new StripePayment(); return stripe.chargeStripe(amount, paymentData.get("card_token")); // Different method! } else if ("paypal".equals(gateway)) { PayPalPayment paypal = new PayPalPayment(); return paypal.processPaypal(amount, paymentData.get("email")); // Different method! } else if ("square".equals(gateway)) { SquarePayment square = new SquarePayment(); return square.makePayment(amount, paymentData.get("payment_method")); // Different method! } else { throw new IllegalArgumentException("Unsupported gateway: " + gateway); } }}
// Problems:// - Client needs to know about all gateway interfaces// - Different method names and parameters for each gateway// - Hard to add new gateways (need to modify PaymentProcessor)// - Payment data format differs for each gatewayProblems:
- Client needs to know about all different gateway interfaces
- Different method names and parameters for each gateway
- Hard to add new gateways - need to modify existing code
- Payment data format differs for each gateway
The Solution: Adapter Pattern
Section titled “The Solution: Adapter Pattern”from abc import ABC, abstractmethodfrom typing import Dict, Any
# Step 1: Define the target interface (what client expects)class PaymentGateway(ABC): """Target interface - unified payment interface"""
@abstractmethod def process_payment(self, amount: float, payment_data: Dict[str, Any]) -> Dict[str, Any]: """Process payment - unified interface""" pass
# Step 2: Incompatible payment gateway classesclass StripePayment: """Stripe payment gateway - incompatible interface!"""
def charge_stripe(self, amount: float, card_token: str) -> Dict[str, Any]: # Stripe-specific implementation print(f"💳 Processing Stripe payment: ${amount} with token {card_token[:10]}...") return { "status": "success", "gateway": "stripe", "transaction_id": f"stripe_{amount}", "amount": amount }
class PayPalPayment: """PayPal payment gateway - incompatible interface!"""
def process_paypal(self, amount: float, email: str) -> Dict[str, Any]: # PayPal-specific implementation print(f"💳 Processing PayPal payment: ${amount} for {email}...") return { "status": "success", "gateway": "paypal", "transaction_id": f"paypal_{amount}", "amount": amount }
class SquarePayment: """Square payment gateway - incompatible interface!"""
def make_payment(self, amount: float, payment_method: str) -> Dict[str, Any]: # Square-specific implementation print(f"💳 Processing Square payment: ${amount} with method {payment_method}...") return { "status": "success", "gateway": "square", "transaction_id": f"square_{amount}", "amount": amount }
# Step 3: Create Adapters for each incompatible gatewayclass StripeAdapter(PaymentGateway): """Adapter for Stripe payment gateway"""
def __init__(self): self.stripe = StripePayment()
def process_payment(self, amount: float, payment_data: Dict[str, Any]) -> Dict[str, Any]: """Adapt Stripe interface to unified PaymentGateway interface""" # Extract Stripe-specific data card_token = payment_data.get("card_token") if not card_token: raise ValueError("Stripe requires 'card_token' in payment_data")
# Call Stripe's incompatible method return self.stripe.charge_stripe(amount, card_token)
class PayPalAdapter(PaymentGateway): """Adapter for PayPal payment gateway"""
def __init__(self): self.paypal = PayPalPayment()
def process_payment(self, amount: float, payment_data: Dict[str, Any]) -> Dict[str, Any]: """Adapt PayPal interface to unified PaymentGateway interface""" # Extract PayPal-specific data email = payment_data.get("email") if not email: raise ValueError("PayPal requires 'email' in payment_data")
# Call PayPal's incompatible method return self.paypal.process_paypal(amount, email)
class SquareAdapter(PaymentGateway): """Adapter for Square payment gateway"""
def __init__(self): self.square = SquarePayment()
def process_payment(self, amount: float, payment_data: Dict[str, Any]) -> Dict[str, Any]: """Adapt Square interface to unified PaymentGateway interface""" # Extract Square-specific data payment_method = payment_data.get("payment_method") if not payment_method: raise ValueError("Square requires 'payment_method' in payment_data")
# Call Square's incompatible method return self.square.make_payment(amount, payment_method)
# Step 4: Client code - uses unified interfaceclass PaymentService: """Client - uses unified PaymentGateway interface"""
def __init__(self): self.adapters = { "stripe": StripeAdapter(), "paypal": PayPalAdapter(), "square": SquareAdapter() }
def process_payment(self, gateway: str, amount: float, payment_data: Dict[str, Any]) -> Dict[str, Any]: """Process payment - unified interface for all gateways!""" adapter = self.adapters.get(gateway.lower()) if not adapter: raise ValueError(f"Unsupported payment gateway: {gateway}")
# All gateways use the same interface! return adapter.process_payment(amount, payment_data)
# Usage - Clean and unified!def main(): payment_service = PaymentService()
# All gateways use the same interface! result1 = payment_service.process_payment( "stripe", 100.0, {"card_token": "tok_1234567890"} ) print(f"✅ {result1}\n")
result2 = payment_service.process_payment( "paypal", 50.0, {"email": "customer@example.com"} ) print(f"✅ {result2}\n")
result3 = payment_service.process_payment( "square", 75.0, {"payment_method": "card_1234"} ) print(f"✅ {result3}\n")
print("🎉 All payments processed through unified interface!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the target interface (what client expects)interface PaymentGateway { // Target interface - unified payment interface Map<String, Object> processPayment(double amount, Map<String, Object> paymentData);}
// Step 2: Incompatible payment gateway classesclass StripePayment { // Stripe payment gateway - incompatible interface! public Map<String, Object> chargeStripe(double amount, String cardToken) { // Stripe-specific implementation System.out.println("💳 Processing Stripe payment: $" + amount + " with token " + cardToken.substring(0, Math.min(10, cardToken.length())) + "..."); Map<String, Object> result = new HashMap<>(); result.put("status", "success"); result.put("gateway", "stripe"); result.put("transaction_id", "stripe_" + amount); result.put("amount", amount); return result; }}
class PayPalPayment { // PayPal payment gateway - incompatible interface! public Map<String, Object> processPaypal(double amount, String email) { // PayPal-specific implementation System.out.println("💳 Processing PayPal payment: $" + amount + " for " + email + "..."); Map<String, Object> result = new HashMap<>(); result.put("status", "success"); result.put("gateway", "paypal"); result.put("transaction_id", "paypal_" + amount); result.put("amount", amount); return result; }}
class SquarePayment { // Square payment gateway - incompatible interface! public Map<String, Object> makePayment(double amount, String paymentMethod) { // Square-specific implementation System.out.println("💳 Processing Square payment: $" + amount + " with method " + paymentMethod + "..."); Map<String, Object> result = new HashMap<>(); result.put("status", "success"); result.put("gateway", "square"); result.put("transaction_id", "square_" + amount); result.put("amount", amount); return result; }}
// Step 3: Create Adapters for each incompatible gatewayclass StripeAdapter implements PaymentGateway { // Adapter for Stripe payment gateway private StripePayment stripe;
public StripeAdapter() { this.stripe = new StripePayment(); }
@Override public Map<String, Object> processPayment(double amount, Map<String, Object> paymentData) { // Adapt Stripe interface to unified PaymentGateway interface String cardToken = (String) paymentData.get("card_token"); if (cardToken == null) { throw new IllegalArgumentException("Stripe requires 'card_token' in payment_data"); }
// Call Stripe's incompatible method return stripe.chargeStripe(amount, cardToken); }}
class PayPalAdapter implements PaymentGateway { // Adapter for PayPal payment gateway private PayPalPayment paypal;
public PayPalAdapter() { this.paypal = new PayPalPayment(); }
@Override public Map<String, Object> processPayment(double amount, Map<String, Object> paymentData) { // Adapt PayPal interface to unified PaymentGateway interface String email = (String) paymentData.get("email"); if (email == null) { throw new IllegalArgumentException("PayPal requires 'email' in payment_data"); }
// Call PayPal's incompatible method return paypal.processPaypal(amount, email); }}
class SquareAdapter implements PaymentGateway { // Adapter for Square payment gateway private SquarePayment square;
public SquareAdapter() { this.square = new SquarePayment(); }
@Override public Map<String, Object> processPayment(double amount, Map<String, Object> paymentData) { // Adapt Square interface to unified PaymentGateway interface String paymentMethod = (String) paymentData.get("payment_method"); if (paymentMethod == null) { throw new IllegalArgumentException("Square requires 'payment_method' in payment_data"); }
// Call Square's incompatible method return square.makePayment(amount, paymentMethod); }}
// Step 4: Client code - uses unified interfaceclass PaymentService { // Client - uses unified PaymentGateway interface private Map<String, PaymentGateway> adapters;
public PaymentService() { this.adapters = new HashMap<>(); adapters.put("stripe", new StripeAdapter()); adapters.put("paypal", new PayPalAdapter()); adapters.put("square", new SquareAdapter()); }
public Map<String, Object> processPayment(String gateway, double amount, Map<String, Object> paymentData) { // Process payment - unified interface for all gateways! PaymentGateway adapter = adapters.get(gateway.toLowerCase()); if (adapter == null) { throw new IllegalArgumentException("Unsupported payment gateway: " + gateway); }
// All gateways use the same interface! return adapter.processPayment(amount, paymentData); }}
// Usage - Clean and unified!public class Main { public static void main(String[] args) { PaymentService paymentService = new PaymentService();
// All gateways use the same interface! Map<String, Object> paymentData1 = new HashMap<>(); paymentData1.put("card_token", "tok_1234567890"); Map<String, Object> result1 = paymentService.processPayment("stripe", 100.0, paymentData1); System.out.println("✅ " + result1 + "\n");
Map<String, Object> paymentData2 = new HashMap<>(); paymentData2.put("email", "customer@example.com"); Map<String, Object> result2 = paymentService.processPayment("paypal", 50.0, paymentData2); System.out.println("✅ " + result2 + "\n");
Map<String, Object> paymentData3 = new HashMap<>(); paymentData3.put("payment_method", "card_1234"); Map<String, Object> result3 = paymentService.processPayment("square", 75.0, paymentData3); System.out.println("✅ " + result3 + "\n");
System.out.println("🎉 All payments processed through unified interface!"); }}Adapter Pattern Variants
Section titled “Adapter Pattern Variants”There are two main ways to implement the Adapter Pattern:
1. Object Adapter (Composition)
Section titled “1. Object Adapter (Composition)”Uses composition - adapter contains an instance of the adaptee:
# Object Adapter - uses compositionclass Target: def request(self): return "Target request"
class Adaptee: def specific_request(self): return "Adaptee specific request"
class Adapter(Target): def __init__(self): self.adaptee = Adaptee() # Composition
def request(self): # Adapt the interface return self.adaptee.specific_request()// Object Adapter - uses compositioninterface Target { String request();}
class Adaptee { public String specificRequest() { return "Adaptee specific request"; }}
class Adapter implements Target { private Adaptee adaptee; // Composition
public Adapter() { this.adaptee = new Adaptee(); }
@Override public String request() { // Adapt the interface return adaptee.specificRequest(); }}Pros: More flexible, can adapt multiple adaptees
Cons: Requires adaptee instance
2. Class Adapter (Inheritance)
Section titled “2. Class Adapter (Inheritance)”Uses inheritance - adapter extends both target and adaptee:
# Class Adapter - uses multiple inheritance (Python supports this)class Target: def request(self): return "Target request"
class Adaptee: def specific_request(self): return "Adaptee specific request"
class Adapter(Target, Adaptee): # Multiple inheritance def request(self): # Adapt the interface return self.specific_request()// Class Adapter - uses inheritance (Java doesn't support multiple inheritance)// This is a limitation - Java can only extend one class// So Object Adapter is preferred in Java
interface Target { String request();}
class Adaptee { public String specificRequest() { return "Adaptee specific request"; }}
// In Java, we'd use composition (Object Adapter) instead// Class adapter works better in languages with multiple inheritancePros: No need for adaptee instance
Cons: Less flexible, language-dependent (Java doesn’t support multiple inheritance)
When to Use Adapter Pattern?
Section titled “When to Use Adapter Pattern?”Use Adapter Pattern when:
✅ Integrating third-party libraries - Library interface doesn’t match your code
✅ Legacy code integration - Old code needs to work with new code
✅ Multiple incompatible interfaces - Need to make them work together
✅ You want to avoid modifying - Don’t want to change existing code
✅ Unified interface needed - Want consistent interface for similar functionality
When NOT to Use Adapter Pattern?
Section titled “When NOT to Use Adapter Pattern?”Don’t use Adapter Pattern when:
❌ You can modify the code - If you can change the incompatible interface, do that instead
❌ Simple interface mismatch - If mismatch is trivial, direct modification might be better
❌ Over-engineering - Don’t add complexity for simple cases
❌ Performance critical - Adapter adds a layer of indirection (usually negligible)
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Modifying Existing Code Instead of Adapting
Section titled “Mistake 1: Modifying Existing Code Instead of Adapting”# ❌ Bad: Modifying existing code to make it compatibleclass ThirdPartyLibrary: def old_method(self, param): return f"Result: {param}"
# Modifying third-party code - risky!class ThirdPartyLibrary: def old_method(self, param): return f"Result: {param}"
def new_method(self, param): # Added method - but you shouldn't modify third-party code! return self.old_method(param)
# ✅ Good: Use Adapter insteadclass Adapter: def __init__(self): self.library = ThirdPartyLibrary() # Use as-is
def new_method(self, param): return self.library.old_method(param) # Adapt the interface// ❌ Bad: Modifying existing code to make it compatibleclass ThirdPartyLibrary { public String oldMethod(String param) { return "Result: " + param; }}
// Modifying third-party code - risky!class ThirdPartyLibrary { public String oldMethod(String param) { return "Result: " + param; }
public String newMethod(String param) { // Added method - but you shouldn't modify third-party code! return oldMethod(param); }}
// ✅ Good: Use Adapter insteadclass Adapter { private ThirdPartyLibrary library; // Use as-is
public Adapter() { this.library = new ThirdPartyLibrary(); }
public String newMethod(String param) { return library.oldMethod(param); // Adapt the interface }}Mistake 2: Creating Adapters for Simple Cases
Section titled “Mistake 2: Creating Adapters for Simple Cases”# ❌ Bad: Creating adapter for trivial caseclass SimpleClass: def method(self, x): return x * 2
class UnnecessaryAdapter: def __init__(self): self.obj = SimpleClass()
def method(self, x): return self.obj.method(x) # Just passes through - unnecessary!
# ✅ Better: Use the class directlyobj = SimpleClass()result = obj.method(5) # Simple and direct!// ❌ Bad: Creating adapter for trivial caseclass SimpleClass { public int method(int x) { return x * 2; }}
class UnnecessaryAdapter { private SimpleClass obj;
public UnnecessaryAdapter() { this.obj = new SimpleClass(); }
public int method(int x) { return obj.method(x); // Just passes through - unnecessary! }}
// ✅ Better: Use the class directlySimpleClass obj = new SimpleClass();int result = obj.method(5); // Simple and direct!Mistake 3: Not Handling Data Transformation
Section titled “Mistake 3: Not Handling Data Transformation”# ❌ Bad: Not transforming data properlyclass Adapter: def __init__(self): self.adaptee = Adaptee()
def request(self, data): # Problem: Not transforming data format! return self.adaptee.specific_request(data) # Data format might not match!
# ✅ Good: Transform data properlyclass Adapter: def __init__(self): self.adaptee = Adaptee()
def request(self, data): # Transform data to match adaptee's expected format transformed_data = self._transform(data) return self.adaptee.specific_request(transformed_data)
def _transform(self, data): # Transform data format return {"old_format": data}// ❌ Bad: Not transforming data properlyclass Adapter { private Adaptee adaptee;
public Adapter() { this.adaptee = new Adaptee(); }
public String request(String data) { // Problem: Not transforming data format! return adaptee.specificRequest(data); // Data format might not match! }}
// ✅ Good: Transform data properlyclass Adapter { private Adaptee adaptee;
public Adapter() { this.adaptee = new Adaptee(); }
public String request(String data) { // Transform data to match adaptee's expected format Map<String, String> transformedData = transform(data); return adaptee.specificRequest(transformedData); }
private Map<String, String> transform(String data) { // Transform data format Map<String, String> result = new HashMap<>(); result.put("old_format", data); return result; }}Benefits of Adapter Pattern
Section titled “Benefits of Adapter Pattern”- Reusability - Use existing classes without modification
- Flexibility - Easy to add new adapters for new incompatible classes
- Separation of Concerns - Adapter handles compatibility, client stays clean
- Open/Closed Principle - Open for extension (new adapters), closed for modification
- Legacy Integration - Integrate old code with new code easily
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Adapter Pattern?
Section titled “What is Adapter Pattern?”Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces.
Why Use It?
Section titled “Why Use It?”- ✅ Integrate incompatible interfaces - Make old and new code work together
- ✅ Use third-party libraries - Library interface doesn’t match your code
- ✅ Avoid modifying code - Don’t want to change existing working code
- ✅ Unified interface - Want consistent interface for similar functionality
- ✅ Legacy integration - Integrate legacy systems with new systems
How It Works?
Section titled “How It Works?”- Define target interface - What the client expects
- Identify adaptee - The incompatible class
- Create adapter - Implements target interface, wraps adaptee
- Transform calls - Adapter converts target calls to adaptee calls
- Client uses adapter - Client uses unified interface
Key Components
Section titled “Key Components”Client → Target Interface → Adapter → Adaptee- Target Interface - What the client expects
- Adaptee - The incompatible class
- Adapter - Bridges target and adaptee
- Client - Uses the target interface
Simple Example
Section titled “Simple Example”class Target: def request(self): pass
class Adaptee: def specific_request(self): pass
class Adapter(Target): def __init__(self): self.adaptee = Adaptee()
def request(self): return self.adaptee.specific_request() # Adapt!When to Use?
Section titled “When to Use?”✅ Integrating third-party libraries
✅ Legacy code integration
✅ Multiple incompatible interfaces
✅ Want to avoid modifying existing code
✅ Need unified interface
When NOT to Use?
Section titled “When NOT to Use?”❌ You can modify the code directly
❌ Simple interface mismatch
❌ Over-engineering simple cases
❌ Performance critical (adds indirection)
Key Takeaways
Section titled “Key Takeaways”- Adapter Pattern = Makes incompatible interfaces compatible
- Target = Interface client expects
- Adaptee = Incompatible class
- Adapter = Bridge between target and adaptee
- Benefit = Reuse code without modification
- Use Case = Third-party libraries, legacy integration
Common Pattern Structure
Section titled “Common Pattern Structure”class Target: def request(self): pass
class Adaptee: def specific_request(self): pass
class Adapter(Target): def __init__(self): self.adaptee = Adaptee()
def request(self): return self.adaptee.specific_request()
# Usageadapter = Adapter()adapter.request() # Works!Remember
Section titled “Remember”- Adapter Pattern bridges incompatible interfaces
- It allows reuse without modification
- Use Object Adapter (composition) in most cases
- Don’t use it for simple cases - avoid over-engineering
- It’s about compatibility, not just wrapping!
Interview Focus: Adapter Pattern
Section titled “Interview Focus: Adapter Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, allowing them to collaborate without modifying their source code.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of when to use it
- Indicates you can explain concepts clearly
2. When to Use Adapter Pattern
Section titled “2. When to Use Adapter Pattern”Must mention:
- ✅ Integrating third-party libraries - Library interface doesn’t match your code
- ✅ Legacy code integration - Old code needs to work with new code
- ✅ Multiple incompatible interfaces - Need to make them work together
- ✅ Avoid modifying code - Don’t want to change existing code
Example scenario to give:
“I’d use Adapter Pattern when integrating a third-party payment gateway like Stripe into my e-commerce system. Stripe has its own interface (
charge_stripe()), but my system expects a unified payment interface (process_payment()). I create a StripeAdapter that implements my payment interface and internally calls Stripe’s methods.”
3. Object Adapter vs Class Adapter
Section titled “3. Object Adapter vs Class Adapter”Must discuss:
- Object Adapter: Uses composition, more flexible
- Class Adapter: Uses inheritance, language-dependent
- Preference: Object Adapter is preferred in most cases
Example to give:
“I prefer Object Adapter because it uses composition, making it more flexible. I can adapt multiple adaptees or change behavior at runtime. Class Adapter uses inheritance, which is less flexible and doesn’t work well in languages like Java that don’t support multiple inheritance.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- Reusability - Use existing classes without modification
- Flexibility - Easy to add new adapters
- Separation of Concerns - Adapter handles compatibility
- Open/Closed Principle - Open for extension, closed for modification
Trade-offs to acknowledge:
- Complexity - Adds a layer of indirection
- Performance - Small overhead (usually negligible)
- Over-engineering risk - Can be overkill for simple cases
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Adapter Pattern and Decorator Pattern?”
A:
“Adapter Pattern changes the interface of an object to make it compatible with another interface. Decorator Pattern adds new behavior to an object without changing its interface. Adapter is about compatibility, Decorator is about adding functionality.”
Q: “When would you use Adapter vs modifying the code directly?”
A:
“I use Adapter when I can’t or shouldn’t modify the code - like third-party libraries, legacy systems, or when modifying would break existing functionality. If I own the code and modification is safe, I might modify directly. But Adapter is safer and follows Open/Closed Principle.”
Q: “How does Adapter Pattern relate to SOLID principles?”
A:
“Adapter Pattern primarily supports the Open/Closed Principle - you can extend functionality (add new adapters) without modifying existing code. It also supports Single Responsibility Principle by separating the adaptation logic into its own class. Additionally, it helps with Dependency Inversion Principle by allowing clients to depend on abstractions (target interface) rather than concrete implementations.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Adapter Pattern clearly in one sentence
- Explain when to use it (with examples)
- Describe Object Adapter vs Class Adapter
- Implement Adapter Pattern from scratch
- Compare with other structural patterns (Decorator, Facade)
- List benefits and trade-offs
- Identify common mistakes
- Give 2-3 real-world examples
- Connect to SOLID principles
- Discuss when NOT to use it
Remember: Adapter Pattern is about making incompatible interfaces work together - bridging the gap between old and new, third-party and your code! 🔌