In this article of design patterns, we will look at the Adapter design pattern. We will see its various use cases with some example and finally we will have the Adapter design pattern in Java. It is one of the structural patterns and we can find its uses in almost all the libraries in JDK and Spring framework.
Adapter Design Pattern
The Adapter design pattern is part of the structural design pattern in Java. It allows two incompatible interfaces, classes, and services to communicate with each other. These incompatible components can talk to each other unless they change their code or the client’s behavior. It takes input from one component (client), converts it to the expected format before giving it to the other component.
We often get confused that the Adapter and the Bridge patterns are the same, however, they are not. The Adapter works on existing incompatible components, whereas the Bridge pattern is an up-front design. We can see the examples of adapter patterns in real life as well. When we travel to European/American countries from India, we need an charging adapter to connect the power to our laptop. The power sockets are different in different countries and hence we need to carry an adapter.
We also known the Adapter pattern as the Wrapper design pattern.
1. How Does The Adapter Design Pattern Works?
Let’s inspect the following image to understand how the adapter pattern works?
As per the above diagram, we can see there are two processes, Process One and Process Two. These two processes are incompatible with each other, but they must communicate with each other. The process One produces “output A” and process Two requires “output B”; this is where the adapter pattern comes in the picture. It acts as a mediator and takes the “output A” and converts it to “output B” and hands it over to process Two.
2. Adapter Pattern Type
There are two types of implementations for Adapter pattern.
- Object adapter pattern.
- Class adapter pattern.
Let’s see what is the difference between these two.
2.1. Object Adapter
- It uses composition to wrap classes or interfaces, or both.
- Due to point#1, it uses delegation to get the work done.
- We use this approach when there is no way to Subclass the class that is going to be adapted as per client’s interface. The class may have declared as “final”.
- We use this approach when, as per situation, the client wants a contract that is not an interface but an abstract class.
2.2 Class Adapter
- It uses inheritance to wrap classes only.
- Due to point#1, it uses Sub-classing to get the work done.
- We use this approach when sub-classing is possible and client expects to contract with an abstract class.
We will use Object adapter implementation for this article. Both are same and you can choose either of them based on your preference.
3. Adapter Design Pattern in Java
Let us understand the adapter pattern using an example in Java. Let’s say we have an interface MediaPlayer
and an implementing concrete class AudioPlayer
. This class can by default play only the file having a “.mp3” extension. We have few additional interfaces and classes.
- Another interface
AdvancedMediaPlayer
for advance media player. - Concrete classes
Mp4MusicPlayer
implementingAdvancedMediaPlayer
to playmp4
format files. - Class
VlcMusicPlayer
implementingAdvancedMediaPlayer
to playvlc
format.
What if we want to make AudioPlayer play mp4 and vlc format files? To achieve this, we will need an adapter class MediaAdapter
which will implement the interface MediaPlayer
and it will use AdvancedMediaPlayer
implementations to play the required format.
The AudioPlayer
will pass the desired file(audio) format to our adapter MediaAdapter
with no knowledge of the actual concrete class that will play this format. The MediaAdapter
class will take care of actual class implementation based on the input format and play that song. Let’s see what are different classes to implement adapter design pattern in Java.
3.1. Media Player
public interface MediaPlayer{
void playMusic(String audioType, String fileName);
}
3.2. Advanced Media Player
public interface AdvancedMediaPlayer {
void playVlcPlayer(String fileName);
void playMp4Player(String fileName);
}
Let’s implement the concrete classes implementing the AdvancedMediaPlayer
interface.
3.3. VLC Music Player
public class VlcMusicPlayer implements AdvancedMediaPlayer {
@Override
public void playVlcPlayer(String fileName) {
System.out.println("Playing vlc file: " + fileName);
}
@Override
public void playMp4Player(String fileName) {
//do nothing
}
}
3.4. MP4 Music Player
public class Mp4MusicPlayer implements AdvancedMediaPlayer {
@Override
public void playVlcPlayer(String fileName) {
//do nothing
}
@Override
public void playMp4Player(String fileName) {
System.out.println("Playing mp4 file: " + fileName);
}
}
Let’s implement the adapter class MediaAdapter
that will implement the MediaPlayer
interface.
3.5. Media Adapter
public class MediaAdapter implements MediaPlayer {
public static final String VLC = "vlc";
public static final String MP_4 = "mp4";
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase(VLC)) {
advancedMusicPlayer = new VlcMusicPlayer();
} else if (audioType.equalsIgnoreCase(MP_4)) {
advancedMusicPlayer = new Mp4MusicPlayer();
}
}
@Override
public void playMusic(String audioType, String fileName) {
if (audioType.equalsIgnoreCase(VLC)) {
advancedMusicPlayer.playVlcPlayer(fileName);
} else if (audioType.equalsIgnoreCase(MP_4)) {
advancedMusicPlayer.playMp4Player(fileName);
}
}
}
3.6. Audio Player
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void playMusic(String audioType, String fileName) {
//the mp3 format is supported by AudioPlayer itself and it doesn't need adapter here.
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file: " + fileName);
}
//to support other formats, we will need the MediaAdapter
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.playMusic(audioType, fileName);
} else {
System.out.println("The given format: " + audioType + " is not supported");
}
}
}
Next, let us implement the demo class AdapterPatternDemo
that will use the AudioPlayer
to play different audio formats. It will print a message (throw an exception) in case the file format isn’t supported.
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.playMusic("mp3", "song1.mp3");
audioPlayer.playMusic("mp4", "song2.mp4");
audioPlayer.playMusic("vlc", "song3.vlc");
audioPlayer.playMusic("xyz", "song4.avi");
}
}
Output
4. Pros and Cons
Here are some advantages and disadvantages of using the adapter design pattern.
4.1 Advantages
- It allows the reusability of an existing code/functionality.
- Second, it permits two or more incompatible objects to interact.
- We can isolate the interface from the data conversion code, thus supporting the Single Responsibility Principle.
- Last, we can introduce new variants for adapters in our application without breaking the existing client code.
4.2. Disadvantages
Some disadvantages of using the adapter design pattern
- There only disadvantage of this pattern is that it increases the overall complexity of the code; it is relatively simpler to change the service class that matches the rest of your code.
5. When to Use Adapter design pattern
- When we want an object to use an existing incompatible interface.
- When we need a mechanism for two existing incompatible interfaces/classes to talk to each other.
- When we want to create a middle layer for creating a connection from b/w your modern code and existing legacy code.
- When we want to create a middle layer for creating a connection from b/w your modern code and 3rd-party code.
Summary
In this post, we talked about the Adapter Design Pattern. We saw some of the real-world examples along with what are some advantages of using this pattern. We also saw a Java implementation for this design pattern. You can always check our GitHub repository for the latest source code. Here are some examples from the JDK / Spring Framework using the same design pattern.