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.