Adapter Pattern
🎯 Explain Like I'm 5...
Imagine you have a really cool American toy that needs American batteries (big round ones), but you only have European batteries (small square ones) at home! 🔋
😟 The Problem:
- • Your toy: Needs big round American batteries
- • Your batteries: Small square European batteries
- • They don't fit! The toy won't work! 😢
💡 The Solution - Use an Adapter!
- • Get a battery adapter! 🔌
- • Put your European batteries IN the adapter
- • Put the adapter IN your toy
- • Now it works! The adapter TRANSLATES between the two! 🎉
🌟 The Key Idea:
An adapter makes two incompatible things work together! Just like a power adapter lets you plug your laptop into different wall sockets around the world! 🌍
🚀 Why Is This Pattern Useful?
- ✨Make old code work with new code without changing it!
- ✨Connect systems that weren't designed to work together!
- ✨Use existing classes even if their interface doesn't match what you need!
📋 Pattern Purpose
The Adapter pattern converts the interface of a class into another interface that clients expect. It allows classes to work together that couldn't otherwise because of incompatible interfaces.
⚡ When to Use Adapter Pattern
- ✓You want to use an existing class, but its interface doesn't match what you need
- ✓You need to create a reusable class that cooperates with unrelated classes
- ✓You want to use several existing subclasses, but adapting their interface by subclassing is impractical
- ✓You're working with legacy code that can't be modified
🔄 Types of Adapters
Class Adapter (Inheritance):
Uses multiple inheritance to adapt one interface to another
Object Adapter (Composition):
Uses composition to wrap the adaptee - more flexible and commonly used
💻 Java Implementations
Example 1: Media Player Adapter
Adapting different audio formats to work with a standard media player interface.
// Target Interface - what we wantpublic interface MediaPlayer { void play(String audioType, String fileName);}// Adaptee Interface - what we have (incompatible)public interface AdvancedMediaPlayer { void playVlc(String fileName); void playMp4(String fileName);}// Concrete Adaptee 1public class VlcPlayer implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { System.out.println("Playing VLC file: " + fileName); } @Override public void playMp4(String fileName) { // Do nothing }}// Concrete Adaptee 2public class Mp4Player implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { // Do nothing } @Override public void playMp4(String fileName) { System.out.println("Playing MP4 file: " + fileName); }}// Adapter - makes AdvancedMediaPlayer compatible with MediaPlayerpublic class MediaAdapter implements MediaPlayer { private AdvancedMediaPlayer advancedPlayer; public MediaAdapter(String audioType) { if (audioType.equalsIgnoreCase("vlc")) { advancedPlayer = new VlcPlayer(); } else if (audioType.equalsIgnoreCase("mp4")) { advancedPlayer = new Mp4Player(); } } @Override public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("vlc")) { advancedPlayer.playVlc(fileName); } else if (audioType.equalsIgnoreCase("mp4")) { advancedPlayer.playMp4(fileName); } }}// Client - uses the adapterpublic class AudioPlayer implements MediaPlayer { private MediaAdapter mediaAdapter; @Override public void play(String audioType, String fileName) { // Built-in support for mp3 if (audioType.equalsIgnoreCase("mp3")) { System.out.println("Playing MP3 file: " + fileName); } // Use adapter for other formats else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) { mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media type: " + audioType); } } public static void main(String[] args) { AudioPlayer player = new AudioPlayer(); player.play("mp3", "song.mp3"); player.play("mp4", "video.mp4"); player.play("vlc", "movie.vlc"); player.play("avi", "film.avi"); }}Example 2: Payment Gateway Adapter
Adapting different payment processors to a common interface.
// Target Interfacepublic interface PaymentProcessor { void processPayment(double amount); boolean validatePayment();}// Adaptee 1 - PayPal (incompatible interface)public class PayPalService { public void sendPayment(double amount) { System.out.println("Processing $" + amount + " via PayPal"); } public boolean checkAccount() { System.out.println("Validating PayPal account..."); return true; }}// Adaptee 2 - Stripe (different incompatible interface)public class StripeService { public void makePayment(double amount) { System.out.println("Processing $" + amount + " via Stripe"); } public boolean verifyCard() { System.out.println("Verifying Stripe card..."); return true; }}// Adapter for PayPalpublic class PayPalAdapter implements PaymentProcessor { private PayPalService payPalService; public PayPalAdapter(PayPalService payPalService) { this.payPalService = payPalService; } @Override public void processPayment(double amount) { payPalService.sendPayment(amount); } @Override public boolean validatePayment() { return payPalService.checkAccount(); }}// Adapter for Stripepublic class StripeAdapter implements PaymentProcessor { private StripeService stripeService; public StripeAdapter(StripeService stripeService) { this.stripeService = stripeService; } @Override public void processPayment(double amount) { stripeService.makePayment(amount); } @Override public boolean validatePayment() { return stripeService.verifyCard(); }}// Client codepublic class PaymentDemo { public static void main(String[] args) { // Use PayPal through adapter PaymentProcessor paypal = new PayPalAdapter(new PayPalService()); if (paypal.validatePayment()) { paypal.processPayment(100.0); } System.out.println(); // Use Stripe through adapter PaymentProcessor stripe = new StripeAdapter(new StripeService()); if (stripe.validatePayment()) { stripe.processPayment(200.0); } // Both use the same interface! processPayment(paypal, 50.0); processPayment(stripe, 75.0); } // Method works with any payment processor! private static void processPayment(PaymentProcessor processor, double amount) { System.out.println("\nProcessing payment..."); if (processor.validatePayment()) { processor.processPayment(amount); } }}🌍 Real-World Examples
- 🔌Power Adapters: Convert electrical plugs between countries
- 💳Card Readers: Adapt memory cards to USB interface
- 🗄️JDBC: Java Database Connectivity adapts different databases
- 🔗API Wrappers: Adapt third-party APIs to your application's interface
- 🏛️Legacy System Integration: Make old systems work with new ones
✅ Benefits
- ✅Single Responsibility: Separates interface conversion from business logic
- ✅Open/Closed Principle: Add new adapters without changing existing code
- ✅Reusability: Allows incompatible classes to work together
- ✅Flexibility: Easy to add new adapters for new requirements
⚠️ Drawbacks
- ⚠️Complexity: Increases number of classes and interfaces
- ⚠️Overhead: May add slight performance overhead
- ⚠️Indirection: Extra layer between client and adaptee
🔑 Key Points to Remember
- 1️⃣Adapter converts one interface to another
- 2️⃣Use composition (object adapter) rather than inheritance when possible
- 3️⃣Makes existing classes work without modifying their source code
- 4️⃣Different from Decorator - Adapter changes interface, Decorator adds behavior
- 5️⃣Different from Facade - Adapter wraps one object, Facade wraps many
💪 Practice Scenarios
- • Create an adapter for different authentication systems (OAuth, LDAP, API Key)
- • Build adapters for different file formats (CSV, JSON, XML) to common data interface
- • Implement adapters for different notification services (Email, SMS, Push)
- • Design adapters for different logging frameworks
- • Create adapters for different image formats to a common image interface