The adapter design pattern falls under the category of a structural design pattern that lets incompatible objects collaborate. It acts as a wrapper between 2 different objects. The adapter catches the call for one object and transforms them to be recognizable by the second object.
Let us understand this with the help of an example of a USB to Ethernet adapter that is used when we have an ethernet interface at one end and the USB interface on the other end. The USB and ethernet are incompatible with each other which is why we require an adapter. The adapter class has a Client class that expects some object type and it has an Adaptee class that offers the same feature but by exposing a different interface. Now to make these both communicate, we have an Adapter class. The client requests the Adapter by using the target interface. The Adapter class translates the request using the Adaptee Interface on the adaptee. The Client receives the results unaware of the adapter’s role. This has been described in the class diagram as shown below:
Class Diagram:
Let us consider that we have a MediaPlayer Interface which is implemented by the AudioPlayer class. The AudioPlayer can play mp3 format by default. Consider another interface AdvancedPlayer that is being implemented by MP4Player class that plays mp4 formats and WAVPlayer that plays wav formats. If we want to make AudioPlayer class play other formats, then we make use of the MediaAdapter class that implements the MediaPlayer Interface and uses the AdvancedPlayer objects for playing the required format. The code implementation of this scenario is as follows:
//MediaPlayer.java
public interface MediaPlayer {
public void play(String format, String file);
}
//AdvancedPlayer.java
public interface AdvancedPlayer {
public void playMp4(String file);
public void playWav(String file);
}
//Mp4Player.java
public class Mp4Player implements AdvancedPlayer{
@Override
public void playMp4(String file) {
System.out.println("MP4 File "+ file + " Playing....");
}
@Override
public void playWav(String file) {
//do nothing
}
}
//WAVPlayer.java
public class WAVPlayer implements AdvancedPlayer{
@Override
public void playMp4(String file) {
//do nothing
}
@Override
public void playWav(String file) {
System.out.println("WAV File "+ file + " Playing....");
}
}
//MediaAdapter.java
public class MediaAdapter implements MediaPlayer {
AdvancedPlayer advancedPlayer;
public MediaAdapter(String format){
if(format.equalsIgnoreCase("mp4") ){
advancedPlayer = new Mp4Player();
}else if(format.equalsIgnoreCase("wav") ){
advancedPlayer = new WAVPlayer();
}
}
@Override
public void play(String format, String file) {
if(format.equalsIgnoreCase("mp4")){
advancedPlayer.playMp4(file);
}
else if(format.equalsIgnoreCase("wav")){
advancedPlayer.playWav(file);
}
}
}
//AudioPlayer.java
public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String format, String file) {
//inbuilt support to play mp3 music files
if(format.equalsIgnoreCase("mp3")){
System.out.println("MP3 file " + file +" Playing...");
}
//Make use of Adapter to support different formats
else if(format.equalsIgnoreCase("wav") || format.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdapter(format);
mediaAdapter.play(format, file);
}
else{
System.out.println("Format not supported");
}
}
}
//Driver.java
public class Driver {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "music1.mp3");
audioPlayer.play("wav", "music2.wav");
audioPlayer.play("mp4", "music3.mp4");
audioPlayer.play("avi", "music4.avi");
}
}
The output of this code would be:
MP3 file music1.mp3 Playing...
WAV File music2.wav Playing...
MP4 File music3.mp4 Playing...
Format not supported
14. What is a Proxy Design Pattern?
Proxy design pattern falls under the category of structural design that represents the functionality of other classes. This pattern lets the developers provide a substitute for another object. This is called a proxy object. This helps to control the access to the original object and allows us to perform many tasks before or after the request reaches the original object.
As shown in the figure above, in this pattern, we have a ServiceInterface interface that has some operation. This interface is being implemented by a Service class and a Proxy class. The Service class has useful business logic and the Proxy class has a reference field pointing to the service object. Once the proxy finishes processing lazy initialization, logging, caching etc, the request will be passed to the service object. And finally, we have a client that works with the services and the proxies by using the interface. This helps to pass proxy objects to any piece of code.