Low Level Design
Adapter Pattern — Bridge Incompatible Interfaces in Java
How the Adapter pattern wraps legacy or third-party classes to make them work with your system's interface — without rewriting existing code.
What is the Adapter Pattern?#
The Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate. It acts as a wrapper that translates one interface into another that clients expect.
Think of it as a plug adapter — the underlying device is the same, but the adapter makes it compatible with a different socket.
You reach for Adapter when:
- You want to reuse an existing class but its interface doesn't match what you need
- You need to integrate legacy or third-party code with a new system
- You want to make unrelated classes work together without modifying either
Real-World Analogy#
Your laptop has a USB-C port, but your headphones have a 3.5mm jack. You can't plug the jack directly into the USB-C port. So you use a USB-C to 3.5mm adapter:
- 🎧 Headphones → 3.5mm plug
- 🔌 Adapter → converts 3.5mm to USB-C
- 💻 Laptop → USB-C input
The headphones didn't change. The laptop didn't change. The adapter bridges the gap.
Class Diagram#
Violation Code — The Problem#
// Your new system expects this interface
class AudioPlayer {
public void play(String audioData) {
System.out.println("Playing: " + audioData);
}
}
// Legacy code with a completely different interface
class LegacySpeaker {
public void makeSound(String data) {
System.out.println("Making sound: " + data);
}
}
// ❌ Client can't use LegacySpeaker through AudioPlayer interface
public class Main {
public static void main(String[] args) {
AudioPlayer player = new AudioPlayer();
player.play("song.mp3");
LegacySpeaker legacy = new LegacySpeaker();
legacy.makeSound("old.wav"); // completely separate — no polymorphism
}
}
Issues:
- Incompatible interface — LegacySpeaker can't be used where AudioPlayer is expected
- Code duplication — you'd have to duplicate LegacySpeaker's logic in AudioPlayer
- Violates OCP — you'd need to modify AudioPlayer to accommodate the legacy code
- Tight coupling — incompatible components can't be swapped
Enhanced Code — Adapter Pattern#
// Target interface — what your system expects
public interface AudioPlayer {
void play(String audioData);
}
// Adaptee — existing class with an incompatible interface
public class LegacySpeaker {
public void makeSound(String data) {
System.out.println("Legacy Speaker: " + data);
}
}
// Adapter — wraps LegacySpeaker and implements AudioPlayer
public class SpeakerAdapter implements AudioPlayer {
private final LegacySpeaker legacySpeaker;
public SpeakerAdapter(LegacySpeaker speaker) {
this.legacySpeaker = speaker;
}
@Override
public void play(String audioData) {
// Translate the call — map play() to makeSound()
legacySpeaker.makeSound(audioData);
}
}
// Client — only knows AudioPlayer, unaware of LegacySpeaker
public class Main {
public static void main(String[] args) {
AudioPlayer player = new SpeakerAdapter(new LegacySpeaker());
player.play("song.mp3"); // ✅ works seamlessly
}
}
LegacySpeaker was never modified. Main is completely decoupled from it.
Common LLD Problems Using Adapter Pattern#
1. Payment Gateway Integration#
- Adapters: StripeAdapter, PayPalAdapter, RazorpayAdapter
- Context: Each gateway has a different API; adapters unify them behind a common PaymentProcessor interface.
2. Third-Party Social Media APIs#
- Adapters: TwitterAdapter, FacebookAdapter, LinkedInAdapter
- Context: Each platform provides different SDKs; adapters provide a unified posting interface.
3. Legacy System Wrapping#
- Adapters: LegacyOrderServiceAdapter
- Context: Wrap outdated systems so they work with modern interfaces without a big rewrite.
4. Logging Framework Integration#
- Adapters: Log4jAdapter, SLF4JAdapter, ConsoleLoggerAdapter
- Context: Integrate various logging frameworks through a consistent application-wide logger interface.
5. Media Player — Multiple Formats#
- Adapters: MP4Adapter, VLCAdapter, AVIAdapter
- Context: Each format is handled through an adapter that conforms to a common MediaPlayer interface.
6. Database Abstraction Layer#
- Adapters: MySQLAdapter, PostgresAdapter, MongoDBAdapter
- Context: Provide a unified query interface across different databases.
7. File Format Conversion#
- Adapters: DocxToPDFAdapter, CSVToJSONAdapter
- Context: Convert data between formats using a common processing interface.
8. Shipping Provider Integration#
- Adapters: FedExAdapter, UPSAdapter, BlueDartAdapter
- Context: Normalise communication with different shipping APIs behind a standard interface.
Adapter vs Facade#
| Adapter | Facade | |
|---|---|---|
| Intent | Make one incompatible interface work | Simplify a complex subsystem |
| Interface change | Translates an existing one | Creates a new simplified one |
| Number of classes | Typically wraps one class | Typically wraps many classes |
| References | Links |
|---|---|
| Article Reference | Refactoring Guru — Adapter |