Table of contents
1.
Introduction
2.
Components of Adapter Design Pattern
3.
Adapter Design Pattern Example
4.
How Adapter Design Pattern works?
5.
Why do we need an Adapter Design Pattern?
6.
When not to use the Adapter Design Pattern?
7.
Frequently Asked Questions
7.1.
Can an Adapter class adapt multiple Adaptee classes?
7.2.
Can the Adapter Design Pattern be used with interfaces instead of classes?
7.3.
Does the Adapter Design Pattern modify the Adaptee class?
8.
Conclusion
Last Updated: Aug 5, 2024
Easy

Adapter Design Pattern in Java

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

The Adapter Design Pattern is a structural design pattern in Java that allows objects with incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, converting the interface of one class into another interface that the client expects. 

Adapter Design Pattern in Java

In this article, we will discuss the components of the Adapter Design Pattern. We will see an example of how it works, understand why we need it, and discuss when not to use it.

Components of Adapter Design Pattern

The Adapter Design Pattern consists of four main components:

1. Target Interface: This is the interface that the client uses and expects to work with. It defines the methods that the client can call to interact with the objects.
 

2. Adaptee: This is the existing class or interface that needs to be adapted to work with the Target Interface. It has a different interface than what the client expects.
 

3. Adapter: This is the class that makes the Adaptee compatible with the Target Interface. It implements the Target Interface and holds an instance of the Adaptee class. The Adapter translates the client's requests into calls that the Adaptee can understand and handle.
 

4. Client: This is the class that interacts with the Target Interface, unaware of the existence of the Adapter and the Adaptee. The client only sees the Target Interface and works with objects through that interface.

Adapter Design Pattern Example

Suppose we have a music player application that can play audio files in the MP3 format. Now, we want to add support for playing audio files in the WAV format, but our music player only works with an interface called `MediaPlayer` that expects MP3 files.

To solve this problem, we can create an Adapter class called `WavAdapter` that implements the `MediaPlayer` interface and holds an instance of the `WavPlayer` class, which knows how to play WAV files.

For example : 

// MediaPlayer interface (Target Interface)
public interface MediaPlayer {
    void play(String fileName);
}
// MP3Player class (Adaptee)
public class MP3Player implements MediaPlayer {
    @Override
    public void play(String fileName) {
        System.out.println("Playing MP3 file: " + fileName);
    }
}
// WavPlayer class (Adaptee)
public class WavPlayer {
    public void playWav(String fileName) {
        System.out.println("Playing WAV file: " + fileName);
    }
}
// WavAdapter class (Adapter)
public class WavAdapter implements MediaPlayer {
    private WavPlayer wavPlayer;
    public WavAdapter(WavPlayer wavPlayer) {
        this.wavPlayer = wavPlayer;
    }
    @Override
    public void play(String fileName) {
        wavPlayer.playWav(fileName);
    }
}
// Client code
public class Client {
    public static void main(String[] args) {
        MediaPlayer mp3Player = new MP3Player();
        mp3Player.play("song.mp3");
        WavPlayer wavPlayer = new WavPlayer();
        MediaPlayer wavAdapter = new WavAdapter(wavPlayer);
        wavAdapter.play("sound.wav");
    }
}


In this example, the `WavAdapter` acts as a bridge between the `MediaPlayer` interface and the `WavPlayer` class. It allows the client to use the `MediaPlayer` interface to play both MP3 and WAV files without modifying the existing code.

How Adapter Design Pattern works?

The Adapter Design Pattern works by creating an Adapter class that sits between the client and the Adaptee. The Adapter class implements the Target Interface, which the client expects to work with. At the same time, the Adapter class holds an instance of the Adaptee class, which has a different interface.

Let’s discuss how the Adapter Design Pattern works:

1. The client interacts with the Target Interface, making calls to its methods.
 

2. The Adapter class receives the client's requests through the Target Interface.
 

3. Inside the Adapter class, the requests are translated into calls that the Adaptee can understand. The Adapter class acts as a translator between the client and the Adaptee.
 

4. The Adapter class invokes the corresponding methods on the Adaptee object it holds.
 

5. The Adaptee performs the requested operations and returns the results to the Adapter.
 

6. The Adapter receives the results from the Adaptee and translates them back into a format that the client expects.
 

7. Finally, the Adapter returns the translated results to the client through the Target Interface.


In this way, the Adapter Design Pattern allows objects with incompatible interfaces to collaborate by providing a translation layer between them. The client remains unaware of the Adaptee's existence and interacts only with the Target Interface, while the Adapter handles the necessary conversions behind the scenes.

Let’s look at a simplified UML diagram representing the Adapter Design Pattern:

 UML diagram

The arrows indicate the direction of the method calls and the flow of communication between the components.

Why do we need an Adapter Design Pattern?

1. Interface Compatibility: When we have existing classes or third-party libraries with interfaces that don't match our system's requirements, the Adapter Design Pattern allows us to create a compatible interface without modifying the existing code. It helps integrate incompatible interfaces seamlessly.
 

2. Code Reusability: The Adapter Design Pattern promotes code reusability by allowing existing classes to be used in new contexts without changing their code. Instead of rewriting or modifying the existing classes, we can create an Adapter that adapts their interface to fit our needs. This saves development time and effort.
 

3. Flexibility and Extensibility: The Adapter Design Pattern provides flexibility and extensibility to our system. It allows us to introduce new classes or components with different interfaces without affecting the existing client code. By creating Adapters, we can easily incorporate new functionality or replace existing classes with updated versions that have different interfaces.
 

4. Loose Coupling: The Adapter Design Pattern helps achieve loose coupling between the client and the Adaptee. The client depends on the Target Interface, not the concrete Adaptee class. This decouples the client from the specific implementation details of the Adaptee, making the system more maintainable and easier to modify in the future.
 

5. Open-Closed Principle: The Adapter Design Pattern adheres to the Open-Closed Principle, which states that classes should be open for extension but closed for modification. By using Adapters, we can extend the functionality of existing classes without modifying their code. This promotes a more stable and flexible system architecture.
 

6. Integration with Legacy Systems: When working with legacy systems or external libraries that have outdated or incompatible interfaces, the Adapter Design Pattern allows us to integrate them into our modern system. By creating Adapters, we can bridge the gap between the old and new interfaces, enabling seamless communication and collaboration.

When not to use the Adapter Design Pattern?

1. Simple Interface Mismatch: If the interface mismatch between the client and the Adaptee is minor and can be easily resolved by modifying the client code or the Adaptee itself, using an Adapter may introduce unnecessary complexity. In such cases, it might be more straightforward to make the necessary changes directly.
 

2. Performance-Critical Situations: The Adapter Design Pattern introduces an additional layer of indirection between the client and the Adaptee. This indirection can slightly impact performance due to the extra method calls and object creation. If your system has strict performance requirements and the overhead of using an Adapter is not acceptable, you may need to consider alternative approaches.
 

3. Tightly Coupled Adapters: If the Adapter class becomes tightly coupled to the Adaptee and requires extensive knowledge of its internal workings, it can make the system harder to maintain and evolve. In such cases, it might be better to refactor the Adaptee to provide a more suitable interface rather than relying on a complex Adapter.
 

4. Limited Adaptee Functionality: If the Adaptee class provides only a small subset of the functionality required by the client, and there is no prospect of extending or modifying the Adaptee, creating an Adapter may not be worthwhile. In this case, it might be more effective to implement the required functionality directly in the client or create a new class from scratch.
 

5. Overuse of Adapters: Excessive use of Adapters can lead to a proliferation of classes and increased complexity in the system. If you find yourself creating Adapters for every small mismatch or incompatibility, it may indicate a deeper design problem. In such cases, it's important to reassess the overall architecture and consider refactoring or redesigning certain components.
 

6. Incompatible Semantics: The Adapter Design Pattern is suitable when the interfaces are incompatible but the underlying semantics of the operations are compatible. If the semantics of the Adaptee & the client are fundamentally different & cannot be reconciled through an Adapter, it may not be the right solution. In such cases, a different design pattern or approach may be more appropriate.
 

It's important to carefully evaluate the specific requirements & constraints of your system before deciding to use the Adapter Design Pattern. Consider the situations, like added complexity, performance impact, & maintainability, & weigh them against the benefits of using an Adapter. In some cases, alternative approaches like refactoring, redesigning, or implementing the required functionality directly may be more suitable.

Frequently Asked Questions

Can an Adapter class adapt multiple Adaptee classes?

Yes, an Adapter class can be designed to adapt multiple Adaptee classes, providing a unified interface to the client.

Can the Adapter Design Pattern be used with interfaces instead of classes?

Yes, the Adapter Design Pattern can be applied to both classes & interfaces, allowing incompatible interfaces to work together.

Does the Adapter Design Pattern modify the Adaptee class?

No, the Adapter Design Pattern does not modify the Adaptee class. Instead, it creates a separate Adapter class that translates between the client & the Adaptee.

Conclusion

In this article, we discussed the Adapter Design Pattern, a structural design pattern in Java that allows incompatible interfaces to work together. We learned about its components, saw an example, understood how it works, & discussed its benefits & scenarios where it may not be suitable. The Adapter Design Pattern is a valuable tool for achieving interface compatibility, code reusability, & flexibility in software systems.

You can also practice coding questions commonly asked in interviews on Coding Ninjas Code360

Also, check out some of the Guided Paths on topics such as Data Structure and AlgorithmsCompetitive ProgrammingOperating SystemsComputer Networks, DBMSSystem Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.

Live masterclass