Adapter Design Pattern

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?

Adapter Design Pattern

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.

  1. Object adapter pattern.
  2. Class adapter pattern.

Let’s see what is the difference between these two.

2.1. Object Adapter

  1. It uses composition to wrap classes or interfaces, or both.
  2. Due to point#1, it uses delegation to get the work done.
  3. 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”.
  4. We use this approach when, as per situation, the client wants a contract that is not an interface but an abstract class.
Object Adapter

2.2 Class Adapter

  1. It uses inheritance to wrap classes only.
  2. Due to point#1, it uses Sub-classing to get the work done.
  3. We use this approach when sub-classing is possible and client expects to contract with an abstract class.
Adapter pattern
Class Adapter

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 implementing AdvancedMediaPlayer to play mp4 format files.
  • Class VlcMusicPlayer implementing AdvancedMediaPlayer to play vlc 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

Adapter Design Pattern in Java
Adapter Design Pattern in Java

4. Pros and Cons

Here are some advantages and disadvantages of using the adapter design pattern.

4.1 Advantages

  1. It allows the reusability of an existing code/functionality.
  2. Second, it permits two or more incompatible objects to interact.
  3. We can isolate the interface from the data conversion code, thus supporting the Single Responsibility Principle.
  4. 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

  1. When we want an object to use an existing incompatible interface.
  2. When we need a mechanism for two existing incompatible interfaces/classes to talk to each other.
  3. When we want to create a middle layer for creating a connection from b/w your modern code and existing legacy code.
  4. 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.