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:
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 Principle// ❌ Without Adapter Pattern - Incompatible interfaces!
class Mp3Player { /** Existing MP3 player - works great! */ playMp3(filename: string): void { console.log(`🎵 Playing MP3: ${filename}`); }}
class Mp4Player { /** MP4 player - different interface! */ playMp4(filename: string): void { console.log(`🎬 Playing MP4: ${filename}`); }}
class VlcPlayer { /** VLC player - yet another interface! */ playVlc(filename: string): void { console.log(`📺 Playing VLC: ${filename}`); }}
// Problem: Client needs to know about all different interfaces!class AudioPlayer { play(audioType: string, filename: string): void { if (audioType === "mp3") { const player = new Mp3Player(); player.playMp3(filename); // Different method name! } else if (audioType === "mp4") { const player = new Mp4Player(); player.playMp4(filename); // Different method name! } else if (audioType === "vlc") { const player = new VlcPlayer(); player.playVlc(filename); // Different method name! } else { throw new Error(`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 Principle// ❌ Without Adapter Pattern - Incompatible interfaces!
#include <iostream>#include <string>#include <stdexcept>
class Mp3Player {public: // Existing MP3 player - works great! void playMp3(const std::string& filename) { std::cout << "🎵 Playing MP3: " << filename << std::endl; }};
class Mp4Player {public: // MP4 player - different interface! void playMp4(const std::string& filename) { std::cout << "🎬 Playing MP4: " << filename << std::endl; }};
class VlcPlayer {public: // VLC player - yet another interface! void playVlc(const std::string& filename) { std::cout << "📺 Playing VLC: " << filename << std::endl; }};
// Problem: Client needs to know about all different interfaces!class AudioPlayer {public: void play(const std::string& audioType, const std::string& filename) { if (audioType == "mp3") { Mp3Player player; player.playMp3(filename); // Different method name! } else if (audioType == "mp4") { Mp4Player player; player.playMp4(filename); // Different method name! } else if (audioType == "vlc") { VlcPlayer player; player.playVlc(filename); // Different method name! } else { throw std::invalid_argument("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 Principle// ❌ Without Adapter Pattern - Incompatible interfaces!
using System;
public class Mp3Player{ // Existing MP3 player - works great! public void PlayMp3(string filename) { Console.WriteLine($"🎵 Playing MP3: {filename}"); }}
public class Mp4Player{ // MP4 player - different interface! public void PlayMp4(string filename) { Console.WriteLine($"🎬 Playing MP4: {filename}"); }}
public class VlcPlayer{ // VLC player - yet another interface! public void PlayVlc(string filename) { Console.WriteLine($"📺 Playing VLC: {filename}"); }}
// Problem: Client needs to know about all different interfaces!public class AudioPlayer{ public void Play(string audioType, string filename) { if (audioType == "mp3") { Mp3Player player = new Mp3Player(); player.PlayMp3(filename); // Different method name! } else if (audioType == "mp4") { Mp4Player player = new Mp4Player(); player.PlayMp4(filename); // Different method name! } else if (audioType == "vlc") { VlcPlayer player = new VlcPlayer(); player.PlayVlc(filename); // Different method name! } else { throw new ArgumentException($"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 Principle// ❌ Without Adapter Pattern - Incompatible interfaces!
package main
import "fmt"
type Mp3Player struct{}
func (p *Mp3Player) PlayMp3(filename string) { fmt.Printf("🎵 Playing MP3: %s\n", filename)}
type Mp4Player struct{}
func (p *Mp4Player) PlayMp4(filename string) { fmt.Printf("🎬 Playing MP4: %s\n", filename)}
type VlcPlayer struct{}
func (p *VlcPlayer) PlayVlc(filename string) { fmt.Printf("📺 Playing VLC: %s\n", filename)}
// Problem: AudioPlayer needs to know about all different types!type AudioPlayer struct{}
func (a *AudioPlayer) Play(audioType, filename string) { switch audioType { case "mp3": player := &Mp3Player{} player.PlayMp3(filename) // Different method name! case "mp4": player := &Mp4Player{} player.PlayMp4(filename) // Different method name! case "vlc": player := &VlcPlayer{} player.PlayVlc(filename) // Different method name! default: fmt.Printf("Unsupported audio type: %s\n", 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 Principle// The Problemstruct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct Checkout { stripe: StripeGateway,}impl Checkout { fn pay(&self, amount: u32) { self.stripe.charge_card(amount); }}Problems:
- 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”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!"); }}// Step 1: Define the target interface (what client expects)interface MediaPlayer { /** Target interface - what the client expects */ play(audioType: string, filename: string): void;}
// Step 2: Existing compatible class (no adapter needed)class Mp3Player implements MediaPlayer { /** MP3 Player - already compatible with MediaPlayer interface */ play(audioType: string, filename: string): void { if (audioType === "mp3") { this.playMp3(filename); } else { throw new Error(`Mp3Player doesn't support ${audioType}`); } }
playMp3(filename: string): void { console.log(`🎵 Playing MP3: ${filename}`); }}
// Step 3: Incompatible classes (need adapters)class Mp4Player { /** MP4 Player - incompatible interface! */ playMp4(filename: string): void { console.log(`🎬 Playing MP4: ${filename}`); }}
class VlcPlayer { /** VLC Player - incompatible interface! */ playVlc(filename: string): void { console.log(`📺 Playing VLC: ${filename}`); }}
// Step 4: Create the Adapterclass MediaAdapter implements MediaPlayer { /** Adapter - makes incompatible interfaces compatible */ private mp4Player: Mp4Player; private vlcPlayer: VlcPlayer;
constructor() { this.mp4Player = new Mp4Player(); this.vlcPlayer = new VlcPlayer(); }
play(audioType: string, filename: string): void { // Adapt the incompatible interfaces to match MediaPlayer interface if (audioType === "mp4") { // Convert play() call to playMp4() call this.mp4Player.playMp4(filename); } else if (audioType === "vlc") { // Convert play() call to playVlc() call this.vlcPlayer.playVlc(filename); } else { throw new Error(`MediaAdapter doesn't support ${audioType}`); } }}
// Step 5: Client code - uses unified interfaceclass AudioPlayer { /** Client - uses MediaPlayer interface */ private mp3Player: Mp3Player; private adapter: MediaAdapter;
constructor() { this.mp3Player = new Mp3Player(); this.adapter = new MediaAdapter(); }
play(audioType: string, filename: string): void { /** Play media - unified interface for all types! */ if (audioType === "mp3") { this.mp3Player.play(audioType, filename); } else if (audioType === "mp4" || audioType === "vlc") { // Use adapter for incompatible players this.adapter.play(audioType, filename); } else { throw new Error(`Unsupported audio type: ${audioType}`); } }}
// Usage - Clean and simple!function main(): void { const 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
console.log("\n✅ All players work through unified interface!");}
main();#include <iostream>#include <string>#include <stdexcept>
// Step 1: Define the target interface (what client expects)class MediaPlayer {public: virtual ~MediaPlayer() = default; // Target interface - what the client expects virtual void play(const std::string& audioType, const std::string& filename) = 0;};
// Step 2: Existing compatible class (no adapter needed)class Mp3Player : public MediaPlayer {public: // MP3 Player - already compatible with MediaPlayer interface void play(const std::string& audioType, const std::string& filename) override { if (audioType == "mp3") { playMp3(filename); } else { throw std::invalid_argument("Mp3Player doesn't support " + audioType); } }
void playMp3(const std::string& filename) { std::cout << "🎵 Playing MP3: " << filename << std::endl; }};
// Step 3: Incompatible classes (need adapters)class Mp4Player {public: // MP4 Player - incompatible interface! void playMp4(const std::string& filename) { std::cout << "🎬 Playing MP4: " << filename << std::endl; }};
class VlcPlayer {public: // VLC Player - incompatible interface! void playVlc(const std::string& filename) { std::cout << "📺 Playing VLC: " << filename << std::endl; }};
// Step 4: Create the Adapterclass MediaAdapter : public MediaPlayer {private: Mp4Player mp4Player; VlcPlayer vlcPlayer;
public: // Adapter - makes incompatible interfaces compatible void play(const std::string& audioType, const std::string& filename) override { // Adapt the incompatible interfaces to match MediaPlayer interface if (audioType == "mp4") { // Convert play() call to playMp4() call mp4Player.playMp4(filename); } else if (audioType == "vlc") { // Convert play() call to playVlc() call vlcPlayer.playVlc(filename); } else { throw std::invalid_argument("MediaAdapter doesn't support " + audioType); } }};
// Step 5: Client code - uses unified interfaceclass AudioPlayer {private: Mp3Player mp3Player; MediaAdapter adapter;
public: void play(const std::string& audioType, const std::string& filename) { // Play media - unified interface for all types! if (audioType == "mp3") { mp3Player.play(audioType, filename); } else if (audioType == "mp4" || audioType == "vlc") { // Use adapter for incompatible players adapter.play(audioType, filename); } else { throw std::invalid_argument("Unsupported audio type: " + audioType); } }};
// Usage - Clean and simple!int main() { AudioPlayer player;
// 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
std::cout << "\n✅ All players work through unified interface!" << std::endl;
return 0;}using System;
// Step 1: Define the target interface (what client expects)public interface IMediaPlayer{ // Target interface - what the client expects void Play(string audioType, string filename);}
// Step 2: Existing compatible class (no adapter needed)public class Mp3Player : IMediaPlayer{ // MP3 Player - already compatible with MediaPlayer interface public void Play(string audioType, string filename) { if (audioType == "mp3") { PlayMp3(filename); } else { throw new ArgumentException($"Mp3Player doesn't support {audioType}"); } }
public void PlayMp3(string filename) { Console.WriteLine($"🎵 Playing MP3: {filename}"); }}
// Step 3: Incompatible classes (need adapters)public class Mp4Player{ // MP4 Player - incompatible interface! public void PlayMp4(string filename) { Console.WriteLine($"🎬 Playing MP4: {filename}"); }}
public class VlcPlayer{ // VLC Player - incompatible interface! public void PlayVlc(string filename) { Console.WriteLine($"📺 Playing VLC: {filename}"); }}
// Step 4: Create the Adapterpublic class MediaAdapter : IMediaPlayer{ // Adapter - makes incompatible interfaces compatible private Mp4Player mp4Player; private VlcPlayer vlcPlayer;
public MediaAdapter() { mp4Player = new Mp4Player(); vlcPlayer = new VlcPlayer(); }
public void Play(string audioType, string filename) { // Adapt the incompatible interfaces to match MediaPlayer interface if (audioType == "mp4") { // Convert Play() call to PlayMp4() call mp4Player.PlayMp4(filename); } else if (audioType == "vlc") { // Convert Play() call to PlayVlc() call vlcPlayer.PlayVlc(filename); } else { throw new ArgumentException($"MediaAdapter doesn't support {audioType}"); } }}
// Step 5: Client code - uses unified interfacepublic class AudioPlayer{ // Client - uses MediaPlayer interface private Mp3Player mp3Player; private MediaAdapter adapter;
public AudioPlayer() { mp3Player = new Mp3Player(); adapter = new MediaAdapter(); }
public void Play(string audioType, string filename) { // Play media - unified interface for all types! if (audioType == "mp3") { mp3Player.Play(audioType, filename); } else if (audioType == "mp4" || audioType == "vlc") { // Use adapter for incompatible players adapter.Play(audioType, filename); } else { throw new ArgumentException($"Unsupported audio type: {audioType}"); } }}
// Usage - Clean and simple!class Program{ static void Main() { 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
Console.WriteLine("\n✅ All players work through unified interface!"); }}package main
import ( "fmt")
// Step 1: Define the target interface (what client expects)type MediaPlayer interface { Play(audioType, filename string)}
// Step 2: Existing compatible struct (no adapter needed)type Mp3Player struct{}
func (p *Mp3Player) Play(audioType, filename string) { if audioType == "mp3" { p.PlayMp3(filename) } else { fmt.Printf("Mp3Player doesn't support %s\n", audioType) }}
func (p *Mp3Player) PlayMp3(filename string) { fmt.Printf("🎵 Playing MP3: %s\n", filename)}
// Step 3: Incompatible structs (need adapters)type Mp4Player struct{}
func (p *Mp4Player) PlayMp4(filename string) { fmt.Printf("🎬 Playing MP4: %s\n", filename)}
type VlcPlayer struct{}
func (p *VlcPlayer) PlayVlc(filename string) { fmt.Printf("📺 Playing VLC: %s\n", filename)}
// Step 4: Create the Adaptertype MediaAdapter struct { mp4Player *Mp4Player vlcPlayer *VlcPlayer}
func NewMediaAdapter() *MediaAdapter { return &MediaAdapter{ mp4Player: &Mp4Player{}, vlcPlayer: &VlcPlayer{}, }}
func (a *MediaAdapter) Play(audioType, filename string) { switch audioType { case "mp4": a.mp4Player.PlayMp4(filename) case "vlc": a.vlcPlayer.PlayVlc(filename) default: fmt.Printf("MediaAdapter doesn't support %s\n", audioType) }}
// Step 5: Client code - uses unified interfacetype AudioPlayer struct { mp3Player *Mp3Player adapter *MediaAdapter}
func NewAudioPlayer() *AudioPlayer { return &AudioPlayer{ mp3Player: &Mp3Player{}, adapter: NewMediaAdapter(), }}
func (a *AudioPlayer) Play(audioType, filename string) { switch audioType { case "mp3": a.mp3Player.Play(audioType, filename) case "mp4", "vlc": a.adapter.Play(audioType, filename) default: fmt.Printf("Unsupported audio type: %s\n", audioType) }}
func main() { player := NewAudioPlayer()
player.Play("mp3", "song.mp3") player.Play("mp4", "movie.mp4") player.Play("vlc", "video.vlc")
fmt.Println("\n✅ All players work through unified interface!")}// Class Structuretrait PaymentProcessor { fn pay(&self, amount: u32);}struct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct StripeAdapter { gateway: StripeGateway,}impl PaymentProcessor for StripeAdapter { fn pay(&self, amount: u32) { self.gateway.charge_card(amount); }}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 gateway// ❌ Without Adapter Pattern - Incompatible payment interfaces!
class StripePayment { /** Stripe payment gateway - incompatible interface! */ chargeStripe(amount: number, cardToken: string): Record<string, any> { // Stripe-specific implementation return {status: "success", gateway: "stripe", transaction_id: `stripe_${amount}`}; }}
class PayPalPayment { /** PayPal payment gateway - different interface! */ processPaypal(amount: number, email: string): Record<string, any> { // PayPal-specific implementation return {status: "success", gateway: "paypal", transaction_id: `paypal_${amount}`}; }}
class SquarePayment { /** Square payment gateway - yet another interface! */ makePayment(amount: number, paymentMethod: string): Record<string, any> { // Square-specific implementation return {status: "success", gateway: "square", transaction_id: `square_${amount}`}; }}
// Problem: Client needs to handle all different interfaces!class PaymentProcessor { processPayment(gateway: string, amount: number, paymentData: Record<string, string>): Record<string, any> { if (gateway === "stripe") { const stripe = new StripePayment(); return stripe.chargeStripe(amount, paymentData["card_token"]); // Different method! } else if (gateway === "paypal") { const paypal = new PayPalPayment(); return paypal.processPaypal(amount, paymentData["email"]); // Different method! } else if (gateway === "square") { const square = new SquarePayment(); return square.makePayment(amount, paymentData["payment_method"]); // Different method! } else { throw new Error(`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!
#include <iostream>#include <map>#include <string>#include <stdexcept>
class StripePayment {public: // Stripe payment gateway - incompatible interface! std::map<std::string, std::string> chargeStripe(double amount, const std::string& cardToken) { // Stripe-specific implementation return { {"status", "success"}, {"gateway", "stripe"}, {"transaction_id", "stripe_" + std::to_string(amount)} }; }};
class PayPalPayment {public: // PayPal payment gateway - different interface! std::map<std::string, std::string> processPaypal(double amount, const std::string& email) { // PayPal-specific implementation return { {"status", "success"}, {"gateway", "paypal"}, {"transaction_id", "paypal_" + std::to_string(amount)} }; }};
class SquarePayment {public: // Square payment gateway - yet another interface! std::map<std::string, std::string> makePayment(double amount, const std::string& paymentMethod) { // Square-specific implementation return { {"status", "success"}, {"gateway", "square"}, {"transaction_id", "square_" + std::to_string(amount)} }; }};
// Problem: Client needs to handle all different interfaces!class PaymentProcessor {public: std::map<std::string, std::string> processPayment( const std::string& gateway, double amount, const std::map<std::string, std::string>& paymentData ) { if (gateway == "stripe") { StripePayment stripe; return stripe.chargeStripe(amount, paymentData.at("card_token")); // Different method! } else if (gateway == "paypal") { PayPalPayment paypal; return paypal.processPaypal(amount, paymentData.at("email")); // Different method! } else if (gateway == "square") { SquarePayment square; return square.makePayment(amount, paymentData.at("payment_method")); // Different method! } else { throw std::invalid_argument("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!
using System;using System.Collections.Generic;
public class StripePayment{ /** Stripe payment gateway - incompatible interface! */ public Dictionary<string, object> ChargeStripe(double amount, string cardToken) { // Stripe-specific implementation return new Dictionary<string, object> { { "status", "success" }, { "gateway", "stripe" }, { "transaction_id", $"stripe_{amount}" } }; }}
public class PayPalPayment{ /** PayPal payment gateway - different interface! */ public Dictionary<string, object> ProcessPaypal(double amount, string email) { // PayPal-specific implementation return new Dictionary<string, object> { { "status", "success" }, { "gateway", "paypal" }, { "transaction_id", $"paypal_{amount}" } }; }}
public class SquarePayment{ /** Square payment gateway - yet another interface! */ public Dictionary<string, object> MakePayment(double amount, string paymentMethod) { // Square-specific implementation return new Dictionary<string, object> { { "status", "success" }, { "gateway", "square" }, { "transaction_id", $"square_{amount}" } }; }}
// Problem: Client needs to handle all different interfaces!public class PaymentProcessor{ public Dictionary<string, object> ProcessPayment(string gateway, double amount, Dictionary<string, string> paymentData) { if (gateway == "stripe") { StripePayment stripe = new StripePayment(); return stripe.ChargeStripe(amount, paymentData["card_token"]); // Different method! } else if (gateway == "paypal") { PayPalPayment paypal = new PayPalPayment(); return paypal.ProcessPaypal(amount, paymentData["email"]); // Different method! } else if (gateway == "square") { SquarePayment square = new SquarePayment(); return square.MakePayment(amount, paymentData["payment_method"]); // Different method! } else { throw new ArgumentException($"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!
package main
import "fmt"
type StripePayment struct{}
func (s *StripePayment) ChargeStripe(amount float64, cardToken string) map[string]interface{} { return map[string]interface{}{ "status": "success", "gateway": "stripe", "transaction_id": fmt.Sprintf("stripe_%.2f", amount), }}
type PayPalPayment struct{}
func (p *PayPalPayment) ProcessPaypal(amount float64, email string) map[string]interface{} { return map[string]interface{}{ "status": "success", "gateway": "paypal", "transaction_id": fmt.Sprintf("paypal_%.2f", amount), }}
type SquarePayment struct{}
func (s *SquarePayment) MakePayment(amount float64, paymentMethod string) map[string]interface{} { return map[string]interface{}{ "status": "success", "gateway": "square", "transaction_id": fmt.Sprintf("square_%.2f", amount), }}
// Problem: Client needs to handle all different interfaces!type PaymentProcessor struct{}
func (p *PaymentProcessor) ProcessPayment(gateway string, amount float64, paymentData map[string]string) map[string]interface{} { switch gateway { case "stripe": stripe := &StripePayment{} return stripe.ChargeStripe(amount, paymentData["card_token"]) case "paypal": paypal := &PayPalPayment{} return paypal.ProcessPaypal(amount, paymentData["email"]) case "square": sq := &SquarePayment{} return sq.MakePayment(amount, paymentData["payment_method"]) default: fmt.Printf("Unsupported gateway: %s\n", gateway) return nil }}
// 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// The Problemstruct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct Checkout { stripe: StripeGateway,}impl Checkout { fn pay(&self, amount: u32) { self.stripe.charge_card(amount); }}Problems:
- 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, ) 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<>(); 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!"); }}// Step 1: Define the target interface (what client expects)interface PaymentGateway { /** Target interface - unified payment interface */ processPayment(amount: number, paymentData: Record<string, any>): Record<string, any>;}
// Step 2: Incompatible payment gateway classesclass StripePayment { /** Stripe payment gateway - incompatible interface! */ chargeStripe(amount: number, cardToken: string): Record<string, any> { // Stripe-specific implementation console.log(`💳 Processing Stripe payment: $${amount} with token ${cardToken.substring(0, 10)}...`); return { status: "success", gateway: "stripe", transaction_id: `stripe_${amount}`, amount: amount }; }}
class PayPalPayment { /** PayPal payment gateway - incompatible interface! */ processPaypal(amount: number, email: string): Record<string, any> { // PayPal-specific implementation console.log(`💳 Processing PayPal payment: $${amount} for ${email}...`); return { status: "success", gateway: "paypal", transaction_id: `paypal_${amount}`, amount: amount }; }}
class SquarePayment { /** Square payment gateway - incompatible interface! */ makePayment(amount: number, paymentMethod: string): Record<string, any> { // Square-specific implementation console.log(`💳 Processing Square payment: $${amount} with method ${paymentMethod}...`); return { status: "success", gateway: "square", transaction_id: `square_${amount}`, amount: amount }; }}
// Step 3: Create Adapters for each incompatible gatewayclass StripeAdapter implements PaymentGateway { /** Adapter for Stripe payment gateway */ private stripe: StripePayment;
constructor() { this.stripe = new StripePayment(); }
processPayment(amount: number, paymentData: Record<string, any>): Record<string, any> { // Adapt Stripe interface to unified PaymentGateway interface const cardToken = paymentData["card_token"]; if (!cardToken) { throw new Error("Stripe requires 'card_token' in payment_data"); }
// Call Stripe's incompatible method return this.stripe.chargeStripe(amount, cardToken); }}
class PayPalAdapter implements PaymentGateway { /** Adapter for PayPal payment gateway */ private paypal: PayPalPayment;
constructor() { this.paypal = new PayPalPayment(); }
processPayment(amount: number, paymentData: Record<string, any>): Record<string, any> { // Adapt PayPal interface to unified PaymentGateway interface const email = paymentData["email"]; if (!email) { throw new Error("PayPal requires 'email' in payment_data"); }
// Call PayPal's incompatible method return this.paypal.processPaypal(amount, email); }}
class SquareAdapter implements PaymentGateway { /** Adapter for Square payment gateway */ private square: SquarePayment;
constructor() { this.square = new SquarePayment(); }
processPayment(amount: number, paymentData: Record<string, any>): Record<string, any> { // Adapt Square interface to unified PaymentGateway interface const paymentMethod = paymentData["payment_method"]; if (!paymentMethod) { throw new Error("Square requires 'payment_method' in payment_data"); }
// Call Square's incompatible method return this.square.makePayment(amount, paymentMethod); }}
// Step 4: Client code - uses unified interfaceclass PaymentService { /** Client - uses unified PaymentGateway interface */ private adapters: Record<string, PaymentGateway>;
constructor() { this.adapters = { "stripe": new StripeAdapter(), "paypal": new PayPalAdapter(), "square": new SquareAdapter() }; }
processPayment(gateway: string, amount: number, paymentData: Record<string, any>): Record<string, any> { /** Process payment - unified interface for all gateways! */ const adapter = this.adapters[gateway.toLowerCase()]; if (!adapter) { throw new Error(`Unsupported payment gateway: ${gateway}`); }
// All gateways use the same interface! return adapter.processPayment(amount, paymentData); }}
// Usage - Clean and unified!function main(): void { const paymentService = new PaymentService();
// All gateways use the same interface! const result1 = paymentService.processPayment( "stripe", 100.0, {card_token: "tok_1234567890"} ); console.log(`✅ ${JSON.stringify(result1)}\n`);
const result2 = paymentService.processPayment( "paypal", 50.0, ); console.log(`✅ ${JSON.stringify(result2)}\n`);
const result3 = paymentService.processPayment( "square", 75.0, {payment_method: "card_1234"} ); console.log(`✅ ${JSON.stringify(result3)}\n`);
console.log("🎉 All payments processed through unified interface!");}
main();#include <iostream>#include <map>#include <string>#include <stdexcept>#include <memory>
// Step 1: Define the target interface (what client expects)class PaymentGateway {public: virtual ~PaymentGateway() = default; // Target interface - unified payment interface virtual std::map<std::string, std::string> processPayment( double amount, const std::map<std::string, std::string>& paymentData ) = 0;};
// Step 2: Incompatible payment gateway classesclass StripePayment {public: // Stripe payment gateway - incompatible interface! std::map<std::string, std::string> chargeStripe(double amount, const std::string& cardToken) { // Stripe-specific implementation std::cout << "💳 Processing Stripe payment: $" << amount << " with token " << cardToken.substr(0, std::min(10, (int)cardToken.length())) << "..." << std::endl; return { {"status", "success"}, {"gateway", "stripe"}, {"transaction_id", "stripe_" + std::to_string(amount)}, {"amount", std::to_string(amount)} }; }};
class PayPalPayment {public: // PayPal payment gateway - incompatible interface! std::map<std::string, std::string> processPaypal(double amount, const std::string& email) { // PayPal-specific implementation std::cout << "💳 Processing PayPal payment: $" << amount << " for " << email << "..." << std::endl; return { {"status", "success"}, {"gateway", "paypal"}, {"transaction_id", "paypal_" + std::to_string(amount)}, {"amount", std::to_string(amount)} }; }};
class SquarePayment {public: // Square payment gateway - incompatible interface! std::map<std::string, std::string> makePayment(double amount, const std::string& paymentMethod) { // Square-specific implementation std::cout << "💳 Processing Square payment: $" << amount << " with method " << paymentMethod << "..." << std::endl; return { {"status", "success"}, {"gateway", "square"}, {"transaction_id", "square_" + std::to_string(amount)}, {"amount", std::to_string(amount)} }; }};
// Step 3: Create Adapters for each incompatible gatewayclass StripeAdapter : public PaymentGateway {private: StripePayment stripe;
public: std::map<std::string, std::string> processPayment( double amount, const std::map<std::string, std::string>& paymentData ) override { // Adapt Stripe interface to unified PaymentGateway interface auto it = paymentData.find("card_token"); if (it == paymentData.end()) { throw std::invalid_argument("Stripe requires 'card_token' in payment_data"); }
// Call Stripe's incompatible method return stripe.chargeStripe(amount, it->second); }};
class PayPalAdapter : public PaymentGateway {private: PayPalPayment paypal;
public: std::map<std::string, std::string> processPayment( double amount, const std::map<std::string, std::string>& paymentData ) override { // Adapt PayPal interface to unified PaymentGateway interface auto it = paymentData.find("email"); if (it == paymentData.end()) { throw std::invalid_argument("PayPal requires 'email' in payment_data"); }
// Call PayPal's incompatible method return paypal.processPaypal(amount, it->second); }};
class SquareAdapter : public PaymentGateway {private: SquarePayment square;
public: std::map<std::string, std::string> processPayment( double amount, const std::map<std::string, std::string>& paymentData ) override { // Adapt Square interface to unified PaymentGateway interface auto it = paymentData.find("payment_method"); if (it == paymentData.end()) { throw std::invalid_argument("Square requires 'payment_method' in payment_data"); }
// Call Square's incompatible method return square.makePayment(amount, it->second); }};
// Step 4: Client code - uses unified interfaceclass PaymentService {private: std::map<std::string, std::unique_ptr<PaymentGateway>> adapters;
public: PaymentService() { adapters["stripe"] = std::make_unique<StripeAdapter>(); adapters["paypal"] = std::make_unique<PayPalAdapter>(); adapters["square"] = std::make_unique<SquareAdapter>(); }
std::map<std::string, std::string> processPayment( const std::string& gateway, double amount, const std::map<std::string, std::string>& paymentData ) { // Process payment - unified interface for all gateways! std::string lowerGateway = gateway; std::transform(lowerGateway.begin(), lowerGateway.end(), lowerGateway.begin(), ::tolower);
auto it = adapters.find(lowerGateway); if (it == adapters.end()) { throw std::invalid_argument("Unsupported payment gateway: " + gateway); }
// All gateways use the same interface! return it->second->processPayment(amount, paymentData); }};
// Usage - Clean and unified!int main() { PaymentService paymentService;
// All gateways use the same interface! auto result1 = paymentService.processPayment("stripe", 100.0, {{"card_token", "tok_1234567890"}}); std::cout << "✅ Payment processed\n" << std::endl;
std::cout << "✅ Payment processed\n" << std::endl;
auto result3 = paymentService.processPayment("square", 75.0, {{"payment_method", "card_1234"}}); std::cout << "✅ Payment processed\n" << std::endl;
std::cout << "🎉 All payments processed through unified interface!" << std::endl;
return 0;}using System;using System.Collections.Generic;
// Step 1: Define the target interface (what client expects)public interface IPaymentGateway{ /** Target interface - unified payment interface */ Dictionary<string, object> ProcessPayment(double amount, Dictionary<string, object> paymentData);}
// Step 2: Incompatible payment gateway classespublic class StripePayment{ /** Stripe payment gateway - incompatible interface! */ public Dictionary<string, object> ChargeStripe(double amount, string cardToken) { // Stripe-specific implementation Console.WriteLine($"💳 Processing Stripe payment: ${amount} with token {cardToken.Substring(0, Math.Min(10, cardToken.Length))}..."); return new Dictionary<string, object> { { "status", "success" }, { "gateway", "stripe" }, { "transaction_id", $"stripe_{amount}" }, { "amount", amount } }; }}
public class PayPalPayment{ /** PayPal payment gateway - incompatible interface! */ public Dictionary<string, object> ProcessPaypal(double amount, string email) { // PayPal-specific implementation Console.WriteLine($"💳 Processing PayPal payment: ${amount} for {email}..."); return new Dictionary<string, object> { { "status", "success" }, { "gateway", "paypal" }, { "transaction_id", $"paypal_{amount}" }, { "amount", amount } }; }}
public class SquarePayment{ /** Square payment gateway - incompatible interface! */ public Dictionary<string, object> MakePayment(double amount, string paymentMethod) { // Square-specific implementation Console.WriteLine($"💳 Processing Square payment: ${amount} with method {paymentMethod}..."); return new Dictionary<string, object> { { "status", "success" }, { "gateway", "square" }, { "transaction_id", $"square_{amount}" }, { "amount", amount } }; }}
// Step 3: Create Adapters for each incompatible gatewaypublic class StripeAdapter : IPaymentGateway{ /** Adapter for Stripe payment gateway */ private StripePayment stripe;
public StripeAdapter() { stripe = new StripePayment(); }
public Dictionary<string, object> ProcessPayment(double amount, Dictionary<string, object> paymentData) { // Adapt Stripe interface to unified PaymentGateway interface if (!paymentData.ContainsKey("card_token")) { throw new ArgumentException("Stripe requires 'card_token' in payment_data"); }
// Call Stripe's incompatible method return stripe.ChargeStripe(amount, (string)paymentData["card_token"]); }}
public class PayPalAdapter : IPaymentGateway{ /** Adapter for PayPal payment gateway */ private PayPalPayment paypal;
public PayPalAdapter() { paypal = new PayPalPayment(); }
public Dictionary<string, object> ProcessPayment(double amount, Dictionary<string, object> paymentData) { // Adapt PayPal interface to unified PaymentGateway interface if (!paymentData.ContainsKey("email")) { throw new ArgumentException("PayPal requires 'email' in payment_data"); }
// Call PayPal's incompatible method return paypal.ProcessPaypal(amount, (string)paymentData["email"]); }}
public class SquareAdapter : IPaymentGateway{ /** Adapter for Square payment gateway */ private SquarePayment square;
public SquareAdapter() { square = new SquarePayment(); }
public Dictionary<string, object> ProcessPayment(double amount, Dictionary<string, object> paymentData) { // Adapt Square interface to unified PaymentGateway interface if (!paymentData.ContainsKey("payment_method")) { throw new ArgumentException("Square requires 'payment_method' in payment_data"); }
// Call Square's incompatible method return square.MakePayment(amount, (string)paymentData["payment_method"]); }}
// Step 4: Client code - uses unified interfacepublic class PaymentService{ /** Client - uses unified PaymentGateway interface */ private Dictionary<string, IPaymentGateway> adapters;
public PaymentService() { adapters = new Dictionary<string, IPaymentGateway> { { "stripe", new StripeAdapter() }, { "paypal", new PayPalAdapter() }, { "square", new SquareAdapter() } }; }
public Dictionary<string, object> ProcessPayment(string gateway, double amount, Dictionary<string, object> paymentData) { /** Process payment - unified interface for all gateways! */ string lowerGateway = gateway.ToLower(); if (!adapters.ContainsKey(lowerGateway)) { throw new ArgumentException($"Unsupported payment gateway: {gateway}"); }
// All gateways use the same interface! return adapters[lowerGateway].ProcessPayment(amount, paymentData); }}
// Usage - Clean and unified!class Program{ static void Main() { PaymentService paymentService = new PaymentService();
// All gateways use the same interface! var result1 = paymentService.ProcessPayment( "stripe", 100.0, new Dictionary<string, object> { { "card_token", "tok_1234567890" } } ); Console.WriteLine($"✅ Payment processed\n");
var result2 = paymentService.ProcessPayment( "paypal", 50.0, ); Console.WriteLine($"✅ Payment processed\n");
var result3 = paymentService.ProcessPayment( "square", 75.0, new Dictionary<string, object> { { "payment_method", "card_1234" } } ); Console.WriteLine($"✅ Payment processed\n");
Console.WriteLine("🎉 All payments processed through unified interface!"); }}package main
import ( "fmt")
// Step 1: Define the target interfacetype PaymentGateway interface { ProcessPayment(amount float64, paymentData map[string]interface{}) map[string]interface{}}
// Step 2: Incompatible payment gateway structstype StripePayment struct{}
func (s *StripePayment) ChargeStripe(amount float64, cardToken string) map[string]interface{} { fmt.Printf("💳 Processing Stripe payment: $%.2f with token %s...\n", amount, cardToken[:10]) return map[string]interface{}{ "status": "success", "gateway": "stripe", "transaction_id": fmt.Sprintf("stripe_%.2f", amount), "amount": amount, }}
type PayPalPayment struct{}
func (p *PayPalPayment) ProcessPaypal(amount float64, email string) map[string]interface{} { fmt.Printf("💳 Processing PayPal payment: $%.2f for %s...\n", amount, email) return map[string]interface{}{ "status": "success", "gateway": "paypal", "transaction_id": fmt.Sprintf("paypal_%.2f", amount), "amount": amount, }}
type SquarePayment struct{}
func (s *SquarePayment) MakePayment(amount float64, paymentMethod string) map[string]interface{} { fmt.Printf("💳 Processing Square payment: $%.2f with method %s...\n", amount, paymentMethod) return map[string]interface{}{ "status": "success", "gateway": "square", "transaction_id": fmt.Sprintf("square_%.2f", amount), "amount": amount, }}
// Step 3: Create Adapters for each incompatible gatewaytype StripeAdapter struct{ stripe *StripePayment }
func NewStripeAdapter() *StripeAdapter { return &StripeAdapter{stripe: &StripePayment{}} }
func (a *StripeAdapter) ProcessPayment(amount float64, paymentData map[string]interface{}) map[string]interface{} { cardToken, ok := paymentData["card_token"].(string) if !ok { fmt.Println("Stripe requires 'card_token' in payment_data") return nil } return a.stripe.ChargeStripe(amount, cardToken)}
type PayPalAdapter struct{ paypal *PayPalPayment }
func NewPayPalAdapter() *PayPalAdapter { return &PayPalAdapter{paypal: &PayPalPayment{}} }
func (a *PayPalAdapter) ProcessPayment(amount float64, paymentData map[string]interface{}) map[string]interface{} { email, ok := paymentData["email"].(string) if !ok { fmt.Println("PayPal requires 'email' in payment_data") return nil } return a.paypal.ProcessPaypal(amount, email)}
type SquareAdapter struct{ square *SquarePayment }
func NewSquareAdapter() *SquareAdapter { return &SquareAdapter{square: &SquarePayment{}} }
func (a *SquareAdapter) ProcessPayment(amount float64, paymentData map[string]interface{}) map[string]interface{} { method, ok := paymentData["payment_method"].(string) if !ok { fmt.Println("Square requires 'payment_method' in payment_data") return nil } return a.square.MakePayment(amount, method)}
// Step 4: Client code - uses unified interfacetype PaymentService struct { adapters map[string]PaymentGateway}
func NewPaymentService() *PaymentService { return &PaymentService{ adapters: map[string]PaymentGateway{ "stripe": NewStripeAdapter(), "paypal": NewPayPalAdapter(), "square": NewSquareAdapter(), }, }}
func (s *PaymentService) ProcessPayment(gateway string, amount float64, paymentData map[string]interface{}) map[string]interface{} { adapter, ok := s.adapters[gateway] if !ok { fmt.Printf("Unsupported payment gateway: %s\n", gateway) return nil } return adapter.ProcessPayment(amount, paymentData)}
func main() { svc := NewPaymentService()
svc.ProcessPayment("stripe", 100.0, map[string]interface{}{"card_token": "tok_1234567890"}) fmt.Println("✅ Payment processed\n")
fmt.Println("✅ Payment processed\n")
svc.ProcessPayment("square", 75.0, map[string]interface{}{"payment_method": "card_1234"}) fmt.Println("✅ Payment processed\n")
fmt.Println("🎉 All payments processed through unified interface!")}// The Solution: Adapter Patterntrait PaymentProcessor { fn pay(&self, amount: u32);}struct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct StripeAdapter { gateway: StripeGateway,}impl PaymentProcessor for StripeAdapter { fn pay(&self, amount: u32) { self.gateway.charge_card(amount); }}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(); }}// Object Adapter - uses compositioninterface Target { request(): string;}
class Adaptee { specificRequest(): string { return "Adaptee specific request"; }}
class Adapter implements Target { private adaptee: Adaptee; // Composition
constructor() { this.adaptee = new Adaptee(); }
request(): string { // Adapt the interface return this.adaptee.specificRequest(); }}#include <string>
// Object Adapter - uses compositionclass Target {public: virtual ~Target() = default; virtual std::string request() = 0;};
class Adaptee {public: std::string specificRequest() { return "Adaptee specific request"; }};
class Adapter : public Target {private: Adaptee adaptee; // Composition
public: std::string request() override { // Adapt the interface return adaptee.specificRequest(); }};// Object Adapter - uses compositionpublic interface ITarget{ string Request();}
public class Adaptee{ public string SpecificRequest() { return "Adaptee specific request"; }}
public class Adapter : ITarget{ private Adaptee adaptee; // Composition
public Adapter() { adaptee = new Adaptee(); }
public string Request() { // Adapt the interface return adaptee.SpecificRequest(); }}package main
// Object Adapter - uses composition (Go idiomatic approach)type Target interface { Request() string}
type Adaptee struct{}
func (a *Adaptee) SpecificRequest() string { return "Adaptee specific request"}
type Adapter struct { adaptee *Adaptee // Composition}
func NewAdapter() *Adapter { return &Adapter{adaptee: &Adaptee{}}}
func (a *Adapter) Request() string { // Adapt the interface return a.adaptee.SpecificRequest()}// 1. Object Adapter Compositiontrait PaymentProcessor { fn pay(&self, amount: u32);}struct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct StripeAdapter { gateway: StripeGateway,}impl PaymentProcessor for StripeAdapter { fn pay(&self, amount: u32) { self.gateway.charge_card(amount); }}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 inheritance// Class Adapter - TypeScript doesn't support multiple inheritance// Use Object Adapter (composition) instead
interface Target { request(): string;}
class Adaptee { specificRequest(): string { return "Adaptee specific request"; }}
// TypeScript doesn't support multiple inheritance of classes// So we use composition (Object Adapter) insteadclass Adapter implements Target { private adaptee: Adaptee = new Adaptee();
request(): string { return this.adaptee.specificRequest(); }}#include <string>
// Class Adapter - uses multiple inheritance (C++ supports this)class Target {public: virtual ~Target() = default; virtual std::string request() { return "Target request"; }};
class Adaptee {public: std::string specificRequest() { return "Adaptee specific request"; }};
class Adapter : public Target, public Adaptee { // Multiple inheritancepublic: std::string request() override { // Adapt the interface return specificRequest(); }};// Class Adapter - C# doesn't support multiple inheritance of classes// Use Object Adapter (composition) instead
public interface ITarget{ string Request();}
public class Adaptee{ public string SpecificRequest() { return "Adaptee specific request"; }}
// C# doesn't support multiple inheritance of classes// So we use composition (Object Adapter) insteadpublic class Adapter : ITarget{ private Adaptee adaptee = new Adaptee();
public string Request() { return adaptee.SpecificRequest(); }}package main
// Go does not support multiple inheritance or class-based inheritance.// Use Object Adapter (composition) instead — the idiomatic Go approach.
type Target interface { Request() string}
type Adaptee struct{}
func (a *Adaptee) SpecificRequest() string { return "Adaptee specific request"}
// Adapter via composition (only option in Go)type Adapter struct { adaptee *Adaptee}
func NewAdapter() *Adapter { return &Adapter{adaptee: &Adaptee{}}}
func (a *Adapter) Request() string { // Adapt the interface return a.adaptee.SpecificRequest()}// 2. Class Adapter Inheritancetrait PaymentProcessor { fn pay(&self, amount: u32);}struct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct StripeAdapter { gateway: StripeGateway,}impl PaymentProcessor for StripeAdapter { fn pay(&self, amount: u32) { self.gateway.charge_card(amount); }}Pros: 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 }}// ❌ Bad: Modifying existing code to make it compatibleclass ThirdPartyLibrary { oldMethod(param: string): string { return `Result: ${param}`; }}
// Modifying third-party code - risky!// Don't do this!
// ✅ Good: Use Adapter insteadclass Adapter { private library: ThirdPartyLibrary;
constructor() { this.library = new ThirdPartyLibrary(); // Use as-is }
newMethod(param: string): string { return this.library.oldMethod(param); // Adapt the interface }}#include <string>
// ❌ Bad: Modifying existing code to make it compatibleclass ThirdPartyLibrary {public: std::string oldMethod(const std::string& param) { return "Result: " + param; }};
// Modifying third-party code - risky!// Don't do this!
// ✅ Good: Use Adapter insteadclass Adapter {private: ThirdPartyLibrary library; // Use as-is
public: std::string newMethod(const std::string& param) { return library.oldMethod(param); // Adapt the interface }};// ❌ Bad: Modifying existing code to make it compatiblepublic class ThirdPartyLibrary{ public string OldMethod(string param) { return $"Result: {param}"; }}
// Modifying third-party code - risky!// Don't do this!
// ✅ Good: Use Adapter insteadpublic class Adapter{ private ThirdPartyLibrary library; // Use as-is
public Adapter() { library = new ThirdPartyLibrary(); }
public string NewMethod(string param) { return library.OldMethod(param); // Adapt the interface }}package main
// ❌ Bad: Modifying existing third-party code to make it compatible
type ThirdPartyLibrary struct{}
func (t *ThirdPartyLibrary) OldMethod(param string) string { return "Result: " + param}
// ✅ Good: Use Adapter insteadtype Adapter struct { library *ThirdPartyLibrary // Use as-is}
func NewAdapter() *Adapter { return &Adapter{library: &ThirdPartyLibrary{}}}
func (a *Adapter) NewMethod(param string) string { return a.library.OldMethod(param) // Adapt the interface}// Mistake 1: Modifying Existing Code Instead of Adaptingstruct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct Checkout { stripe: StripeGateway,}impl Checkout { fn pay(&self, amount: u32) { self.stripe.charge_card(amount); }}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!// ❌ Bad: Creating adapter for trivial caseclass SimpleClass { method(x: number): number { return x * 2; }}
class UnnecessaryAdapter { private obj: SimpleClass;
constructor() { this.obj = new SimpleClass(); }
method(x: number): number { return this.obj.method(x); // Just passes through - unnecessary! }}
// ✅ Better: Use the class directlyconst obj = new SimpleClass();const 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: int method(int x) { return obj.method(x); // Just passes through - unnecessary! }};
// ✅ Better: Use the class directly// SimpleClass obj;// int result = obj.method(5); // Simple and direct!// ❌ Bad: Creating adapter for trivial casepublic class SimpleClass{ public int Method(int x) { return x * 2; }}
public class UnnecessaryAdapter{ private SimpleClass obj;
public UnnecessaryAdapter() { obj = new SimpleClass(); }
public int Method(int x) { return obj.Method(x); // Just passes through - unnecessary! }}
// ✅ Better: Use the class directly// SimpleClass obj = new SimpleClass();// int result = obj.Method(5); // Simple and direct!package main
// ❌ Bad: Creating adapter for trivial casetype SimpleStruct struct{}
func (s *SimpleStruct) Method(x int) int { return x * 2}
type UnnecessaryAdapter struct { obj *SimpleStruct}
func (a *UnnecessaryAdapter) Method(x int) int { return a.obj.Method(x) // Just passes through - unnecessary!}
// ✅ Better: Use the struct directly// obj := &SimpleStruct{}// result := obj.Method(5) // Simple and direct!// Mistake 2: Creating Adapters for Simple Casesstruct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct Checkout { stripe: StripeGateway,}impl Checkout { fn pay(&self, amount: u32) { self.stripe.charge_card(amount); }}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; }}class Adaptee { specificRequest(data: any): string { return `Processing: ${data.old_format}`; }}
// ❌ Bad: Not transforming data properlyclass BadAdapter { private adaptee: Adaptee;
constructor() { this.adaptee = new Adaptee(); }
request(data: string): string { // Problem: Not transforming data format! return this.adaptee.specificRequest(data as any); // Data format might not match! }}
// ✅ Good: Transform data properlyclass GoodAdapter { private adaptee: Adaptee;
constructor() { this.adaptee = new Adaptee(); }
request(data: string): string { // Transform data to match adaptee's expected format const transformedData = this.transform(data); return this.adaptee.specificRequest(transformedData); }
private transform(data: string): Record<string, string> { // Transform data format return {old_format: data}; }}#include <string>#include <map>
class Adaptee {public: std::string specificRequest(const std::map<std::string, std::string>& data) { return "Processing: " + data.at("old_format"); }};
// ❌ Bad: Not transforming data properlyclass BadAdapter {private: Adaptee adaptee;
public: std::string request(const std::string& data) { // Problem: Not transforming data format! // This won't compile - demonstrates the issue // return adaptee.specificRequest(data); return "Error: data format mismatch"; }};
// ✅ Good: Transform data properlyclass GoodAdapter {private: Adaptee adaptee;
std::map<std::string, std::string> transform(const std::string& data) { // Transform data format return {{"old_format", data}}; }
public: std::string request(const std::string& data) { // Transform data to match adaptee's expected format auto transformedData = transform(data); return adaptee.specificRequest(transformedData); }};using System.Collections.Generic;
public class Adaptee{ public string SpecificRequest(Dictionary<string, string> data) { return $"Processing: {data["old_format"]}"; }}
// ❌ Bad: Not transforming data properlypublic class BadAdapter{ private Adaptee adaptee;
public BadAdapter() { adaptee = new Adaptee(); }
public string Request(string data) { // Problem: Not transforming data format! // return adaptee.SpecificRequest(data); // Won't compile - data format mismatch! return "Error: data format mismatch"; }}
// ✅ Good: Transform data properlypublic class GoodAdapter{ private Adaptee adaptee;
public GoodAdapter() { adaptee = new Adaptee(); }
public string Request(string data) { // Transform data to match adaptee's expected format var transformedData = Transform(data); return adaptee.SpecificRequest(transformedData); }
private Dictionary<string, string> Transform(string data) { // Transform data format return new Dictionary<string, string> { { "old_format", data } }; }}package main
type Adaptee struct{}
func (a *Adaptee) SpecificRequest(data map[string]string) string { return "Processing: " + data["old_format"]}
// ❌ Bad: Not transforming data properlytype BadAdapter struct { adaptee *Adaptee}
func (a *BadAdapter) Request(data string) string { // Problem: Not transforming data format! // adaptee.SpecificRequest expects map[string]string, not a plain string return "Error: data format mismatch"}
// ✅ Good: Transform data properlytype GoodAdapter struct { adaptee *Adaptee}
func NewGoodAdapter() *GoodAdapter { return &GoodAdapter{adaptee: &Adaptee{}}}
func (a *GoodAdapter) Request(data string) string { // Transform data to match adaptee's expected format transformed := a.transform(data) return a.adaptee.SpecificRequest(transformed)}
func (a *GoodAdapter) transform(data string) map[string]string { return map[string]string{"old_format": data}}// Mistake 3: Not Handling Data Transformationstruct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct Checkout { stripe: StripeGateway,}impl Checkout { fn pay(&self, amount: u32) { self.stripe.charge_card(amount); }}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!interface Target { String request(); }
class Adaptee { String specificRequest() { return "adapted"; }}
class Adapter implements Target { private Adaptee adaptee = new Adaptee(); public String request() { return adaptee.specificRequest(); // Adapt! }}interface Target { request(): string; }
class Adaptee { specificRequest(): string { return "adapted"; }}
class Adapter implements Target { private adaptee = new Adaptee(); request(): string { return this.adaptee.specificRequest(); // Adapt! }}class Target {public: virtual std::string request() = 0;};
class Adaptee {public: std::string specificRequest() { return "adapted"; }};
class Adapter : public Target { Adaptee adaptee;public: std::string request() override { return adaptee.specificRequest(); // Adapt! }};interface ITarget { string Request(); }
class Adaptee { public string SpecificRequest() => "adapted";}
class Adapter : ITarget { private Adaptee adaptee = new(); public string Request() => adaptee.SpecificRequest(); // Adapt!}type Target interface { Request() string }
type Adaptee struct{}func (a *Adaptee) SpecificRequest() string { return "adapted" }
type Adapter struct{ adaptee *Adaptee }func (a *Adapter) Request() string { return a.adaptee.SpecificRequest() // Adapt!}// Simple Exampletrait PaymentProcessor { fn pay(&self, amount: u32);}struct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct StripeAdapter { gateway: StripeGateway,}impl PaymentProcessor for StripeAdapter { fn pay(&self, amount: u32) { self.gateway.charge_card(amount); }}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!interface Target { String request(); }
class Adaptee { String specificRequest() { return "adapted"; }}
class Adapter implements Target { private Adaptee adaptee = new Adaptee(); public String request() { return adaptee.specificRequest(); }}
// UsageTarget adapter = new Adapter();adapter.request(); // Works!interface Target { request(): string; }
class Adaptee { specificRequest(): string { return "adapted"; }}
class Adapter implements Target { private adaptee = new Adaptee(); request(): string { return this.adaptee.specificRequest(); }}
// Usageconst adapter = new Adapter();adapter.request(); // Works!class Target {public: virtual std::string request() = 0;};
class Adaptee {public: std::string specificRequest() { return "adapted"; }};
class Adapter : public Target { Adaptee adaptee;public: std::string request() override { return adaptee.specificRequest(); }};
// UsageTarget* adapter = new Adapter();adapter->request(); // Works!interface ITarget { string Request(); }
class Adaptee { public string SpecificRequest() => "adapted";}
class Adapter : ITarget { private Adaptee adaptee = new(); public string Request() => adaptee.SpecificRequest();}
// UsageITarget adapter = new Adapter();adapter.Request(); // Works!type Target interface { Request() string }
type Adaptee struct{}func (a *Adaptee) SpecificRequest() string { return "adapted" }
type Adapter struct{ adaptee *Adaptee }func NewAdapter() *Adapter { return &Adapter{adaptee: &Adaptee{}} }func (a *Adapter) Request() string { return a.adaptee.SpecificRequest() }
// Usagevar adapter Target = NewAdapter()adapter.Request() // Works!// Common Pattern Structuretrait PaymentProcessor { fn pay(&self, amount: u32);}struct StripeGateway;impl StripeGateway { fn charge_card(&self, cents: u32) { println!("Stripe {}", cents); }}struct StripeAdapter { gateway: StripeGateway,}impl PaymentProcessor for StripeAdapter { fn pay(&self, amount: u32) { self.gateway.charge_card(amount); }}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! 🔌