The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by converting the interface of a class into another expected by the client. In simpler terms, it allows a system to integrate or “talk” to another system or class without altering the code of either system.
Key Concepts of Adapter Pattern:
- Client: The system that expects to interact with a particular interface.
- Target Interface: The interface that the client expects to work with.
- Adaptee: The existing class or system that needs to be adapted.
- Adapter: The class that implements the target interface and translates requests to the adaptee.
Why is it Used?
In real-world software development, different systems or components often use different interfaces, and direct integration might not be possible. The adapter pattern helps to integrate these systems without requiring significant changes to the existing codebase. It’s typically used when:
- You want to make two incompatible interfaces work together.
- You want to wrap a legacy class with a new interface.
- You need to standardize different interfaces in an application.
How Does It Work?
The adapter pattern works by providing a wrapper (the adapter) that:
- Converts the calls from the client into the format that the adaptee understands.
- Calls the adaptee’s methods and converts the results back to the format expected by the client.
This means that the client does not need to know anything about the internal workings of the adaptee.
Types of Adapters
There are two main types of adapters:
- Object Adapter: Uses composition to adapt an interface. The adapter holds an instance of the adaptee and delegates the calls to it.
- Class Adapter: Uses inheritance to adapt an interface. The adapter inherits the interface of the adaptee and overrides methods as needed.
Example in Code (Object Adapter)
Consider a scenario where you want to integrate a legacy system with a new system that expects a different interface. Below is an example using an object adapter in Java.
// Target interface: Expected by the client
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee: A legacy system with a different interface
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(AdvancedMediaPlayer advancedMusicPlayer) {
this.advancedMusicPlayer = advancedMusicPlayer;
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
// Adaptee: Legacy system class with different interface
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
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
}
}
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);
}
}
// Client
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing MP3 file: " + fileName);
} else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType.equalsIgnoreCase("vlc") ?
new VlcPlayer() : new Mp4Player());
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media type: " + audioType);
}
}
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
Explanation:
- MediaPlayer is the target interface expected by the client (AudioPlayer).
- AdvancedMediaPlayer is the adaptee interface with its own methods for playing VLC and MP4 files.
- MediaAdapter is the adapter class that makes the adaptee (
VlcPlayer
orMp4Player
) compatible with theMediaPlayer
interface by delegating the calls to the appropriate adaptee.
Benefits of Adapter Pattern:
- Reusability: You can reuse the adaptee without modifying it.
- Flexibility: It allows different interfaces to work together.
- Loose coupling: The client is decoupled from the adaptee, which means it can work with different classes as long as they implement the target interface.
- Code organization: Adapter classes keep integration logic separate, making the system easier to understand and maintain.
Drawbacks:
- Complexity: Introducing adapters can add unnecessary layers to a simple system, making it more complicated.
- Overhead: If overused, adapter patterns can increase the number of classes in the system, leading to a bloated design.
When to Use Adapter Pattern:
- When you have legacy code with interfaces that cannot be modified but need to integrate with new components.
- When the system needs to interact with external libraries with incompatible interfaces.
- When you need to provide a consistent interface to clients while dealing with a variety of different interfaces behind the scenes.
The Adapter Pattern is a powerful tool for integrating diverse systems and making disparate components work together smoothly without modifying their original code.