The Adapter Pattern in Java is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by providing a wrapper or adapter class that converts the interface of a class into another interface that a client expects.
Key Components
- Target: The interface that the client expects or uses.
- Adaptee: The existing class with an incompatible interface that needs to be adapted.
- Adapter: A class that implements the Target interface and translates calls to the Adaptee’s interface.
- Client: The code that interacts with the Target interface.
Types of Adapter Pattern
- Object Adapter: Uses composition to hold an instance of the Adaptee and delegate calls to it (more flexible).
- Class Adapter: Uses inheritance, where the Adapter inherits from both the Target and Adaptee (less common, requires multiple inheritance, not supported in languages like Java).
How It Works
- The client interacts with the Adapter through the Target interface.
- The Adapter translates the client’s requests into calls to the Adaptee’s methods.
- The Adaptee performs the actual work, unaware of the Adapter.
Sample Implementation(Object Adapter)
// Target Interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee (Incompatible Interface)
class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("Playing VLC file: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("Playing MP4 file: " + fileName);
}
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
advancedPlayer = new AdvancedMediaPlayer();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedPlayer.playMp4(fileName);
} else {
System.out.println("Invalid media type: " + audioType);
}
}
}
// Client
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing MP3 file: " + fileName);
} else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Unsupported format: " + audioType);
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
AudioPlayer player = new AudioPlayer();
player.play("mp3", "song.mp3");
player.play("vlc", "movie.vlc");
player.play("mp4", "video.mp4");
player.play("avi", "clip.avi");
}
}Code language: JavaScript (javascript)
Sample Implementation(Class Adapter)
// Target Interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee (Incompatible Interface)
class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("Playing VLC file: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("Playing MP4 file: " + fileName);
}
}
// Class Adapter (Extends Adaptee and Implements Target)
class MediaAdapter extends AdvancedMediaPlayer implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
playVlc(fileName); // Call inherited method
} else if (audioType.equalsIgnoreCase("mp4")) {
playMp4(fileName); // Call inherited method
} else {
System.out.println("Invalid media type: " + audioType);
}
}
}
// Client
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing MP3 file: " + fileName);
} else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter();
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Unsupported format: " + audioType);
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
AudioPlayer player = new AudioPlayer();
player.play("mp3", "song.mp3");
player.play("vlc", "movie.vlc");
player.play("mp4", "video.mp4");
player.play("avi", "clip.avi");
}
}Code language: JavaScript (javascript)
Use case and Implementation
Integrating third-party payment gateways with the bank’s existing payment processing system.
To integrate third-party payment gateways with the bank’s existing payment processing system using the Adapter pattern in Java, we can proceed with the following implementation:
Scenario Explanation
Imagine the bank has an established payment processing system that expects a specific interface for processing payments. However, there are multiple third-party payment gateways that the bank wants to integrate, each with its own unique interface. The Adapter pattern will allow us to create adapters for each third-party payment gateway to conform to the bank’s payment processing interface seamlessly.
//PaymentClient.java
// Third-party payment gateway A
class ThirdPartyPaymentGatewayA {
public void processPayment(double amount) {
// Simulating third-party payment gateway A's payment processing logic
System.out.println("Third-Party Payment Gateway A is processing payment of $" + amount);
}
}
// Third-party payment gateway B
class ThirdPartyPaymentGatewayB {
public void makePayment(double amount) {
// Simulating third-party payment gateway B's payment processing logic
System.out.println("Third-Party Payment Gateway B is processing payment of $" + amount);
}
}
// Target interface
interface PaymentProcessor {
void pay(double amount);
}
// Bank's existing payment processor
class BankPaymentProcessor implements PaymentProcessor {
@Override
public void pay(double amount) {
// Implementation of payment processing logic
System.out.println("Bank's payment processor is processing payment of $" + amount);
}
}
// Adapter for Third-Party Payment Gateway A
class PaymentGatewayAAdapter implements PaymentProcessor {
private ThirdPartyPaymentGatewayA gatewayA;
public PaymentGatewayAAdapter(ThirdPartyPaymentGatewayA gatewayA) {
this.gatewayA = gatewayA;
}
@Override
public void pay(double amount) {
// Adapt the method call to third-party gateway A's API
gatewayA.processPayment(amount);
}
}
// Adapter for Third-Party Payment Gateway B
class PaymentGatewayBAdapter implements PaymentProcessor {
private ThirdPartyPaymentGatewayB gatewayB;
public PaymentGatewayBAdapter(ThirdPartyPaymentGatewayB gatewayB) {
this.gatewayB = gatewayB;
}
@Override
public void pay(double amount) {
// Adapt the method call to third-party gateway B's API
gatewayB.makePayment(amount);
}
}
public class PaymentClient {
public static void main(String[] args) {
// Bank's existing payment processor
PaymentProcessor bankProcessor = new BankPaymentProcessor();
// Third-party payment gateways
ThirdPartyPaymentGatewayA gatewayA = new ThirdPartyPaymentGatewayA();
ThirdPartyPaymentGatewayB gatewayB = new ThirdPartyPaymentGatewayB();
// Adapters for third-party payment gateways
PaymentProcessor adapterA = new PaymentGatewayAAdapter(gatewayA);
PaymentProcessor adapterB = new PaymentGatewayBAdapter(gatewayB);
// Process payments through different adapters
bankProcessor.pay(1000); // Using bank's processor
adapterA.pay(500); // Using adapter for gateway A
adapterB.pay(700); // Using adapter for gateway B
}
}
/*
C:\>javac PaymentClient.java
C:\>java PaymentClient
Bank's payment processor is processing payment of $1000.0
Third-Party Payment Gateway A is processing payment of $500.0
Third-Party Payment Gateway B is processing payment of $700.0
*/Advantages
- Reusability: Allows reuse of existing classes with incompatible interfaces.
- Flexibility: The Adapter can add or modify behavior during translation.
- Single Responsibility: Separates interface conversion from business logic.
- Interoperability: Enables integration of legacy or third-party code.
Disadvantages
- Complexity: Adds an extra layer of abstraction, increasing code complexity.
- Overhead: May introduce slight performance overhead due to additional calls.
- Maintenance: Changes to the Adaptee’s interface require updates to the Adapter.
When to Use
- When you need to use an existing class, but its interface is incompatible with the client’s expectations.
- When integrating third-party libraries or legacy code with a different interface.
- When you want to create a reusable class that cooperates with unrelated or unforeseen classes.
Real-World Example
- Converting a USB-C port to a USB-A port using a physical adapter.
- Java’s Arrays.asList() method, which adapts an array to a List interface.
- Wrapping a legacy database API to work with a modern application’s interface.
By using the Adapter pattern, the bank can integrate multiple third-party payment gateways into its existing system without needing to modify its core payment processing logic, thus maintaining compatibility and flexibility in handling various payment services.
