1. Definition
The Adapter Design Pattern acts as a bridge between two incompatible interfaces. This pattern involves a single class called Adapter which joins functionalities of independent or incompatible interfaces.
2. Problem Statement
Imagine you have a music player application that only plays MP3 files. Now, you want to enhance the application to play other formats like MP4 and VLC without changing the existing code of the player.
3. Solution
The Adapter Pattern helps by providing a wrapper class that will convert the new formats to the format acceptable by the player without changing its source code.
4. Real-World Use Cases
1. Reading various types of image files in graphic editors.
2. Chargers that allow a single electronic device to be charged in various countries irrespective of the plug point design.
3. Java’s Arrays.asList() method, which acts as an adapter between arrays and collections.
5. Implementation Steps
1. Identify the existing interface you want to adapt, which, in our case, is the MediaPlayer interface.
2. Understand the interface you want to adapt to.
3. Create an adapter class that implements the original interface.
4. In the adapter class, instantiate or reference an object of the new type.
5. Implement the methods of the original interface in the adapter class in such a way that they delegate to the methods of the new type.
6. Implementation
// Existing MediaPlayer interface
public interface MediaPlayer {
void play(String audioType, String fileName);
}
// AdvancedMediaPlayer Interface
public interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// Concrete classes implementing AdvancedMediaPlayer
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: " + fileName);
}
@Override
public void playMp4(String fileName) {
// do nothing
}
}
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
// do nothing
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: " + fileName);
}
}
// Adapter class
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if(audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if(audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
// AudioPlayer using adapter
public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file. Name: " + fileName);
} else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
// Client code
public class Client {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("mp4", "video.mp4");
audioPlayer.play("vlc", "movie.vlc");
audioPlayer.play("avi", "video.avi");
}
}
Output:
Playing mp3 file. Name: song.mp3 Playing mp4 file. Name: video.mp4 Playing vlc file. Name: movie.vlc Invalid media. avi format not supported
Explanation:
In the Adapter Pattern for the MediaPlayer, MediaAdapter is the adapter that allows AudioPlayer to use the newer AdvancedMediaPlayer functionality without changing its existing interface. When the AudioPlayer needs to play formats other than MP3, it uses the MediaAdapter to handle those formats.
7. When to use?
1. Use the Adapter pattern when you want to use an existing class, and its interface is not compatible with the one you require.
2. When you want to create a reusable class that cooperates with unrelated or unforeseen classes, i.e., classes that don’t necessarily have compatible interfaces.
3. When you want to use several existing subclasses but it’s impractical to adapt their interface by subclassing every one. An adapter can adapt the interface of its parent class.