Skip to main content

Adapter Design Pattern

Problem​

You: Hi Shubh, today I'm facing a huge problem.

Shubh: What problem? Please tell me, let's see if I can help you.

You: Recently, my client asked me to integrate certain payment methods like Google Pay and PayPal. I did that and finally created a makePayment() method which handles all the payment processing. So now, for any payment gateway, I just need to call that method.

Shubh: You did a good job, my friend!

You: Hold on. The problem is that now I also need to add a Stripe payment gateway, which is already integrated into the codebase (it's legacy code). Since it's already set up by another developer, it has a method charge() for payment processing.

You: So, I have to create a separate object for Stripe and call charge() for payments. This is making my code tightly coupled with Stripe payments. Let me show you the diagram.

Problem without Adapter

Problem without Adapter

Shubh: Now I understand your problem. Just use the Adapter Design Pattern. It is generally used when you have to interact with legacy code or make two functions compatible with each other.

You: Can you please explain it?

Shubh: Surely.

UML​

Adapter Design Pattern

UML for Adapter Design Pattern

Shubh: Whenever we travel to different countries, for charging our devices, we use an adapter. The adapter converts the plug type and voltage from our charger's standard to the country's specific standard.

Similarly, Stripe was not compatible with PaymentGateway. Hence, we added an adapter between them to make them compatible with each other.

Code Implementation​

PaymentGateway Interface​

PaymentGateway.java
public interface PaymentGateway {
void makePayment(double amount);
}

PayPal Class​

Paypal.java
public class Paypal implements PaymentGateway {
public void makePayment(double amount) {
System.out.println("Paid: " + amount + " via PayPal");
}
}

GooglePay Class​

GooglePay.java
public class GooglePay implements PaymentGateway {
public void makePayment(double amount) {
System.out.println("Paid: " + amount + " via GooglePay");
}
}

Stripe Class (Legacy Code)​

Stripe.java
public class Stripe {
public void charge(double amount) {
System.out.println("Paid: " + amount + " via Stripe");
}
}

StripeAdapter Class​

StripeAdapter.java
public class StripeAdapter implements PaymentGateway {
private Stripe stripeGateway;

StripeAdapter(Stripe stripeGateway) {
this.stripeGateway = stripeGateway;
}

@Override
public void makePayment(double amount) {
this.stripeGateway.charge(amount);
}
}

Main Class (Client Code)​

Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) {
PaymentGateway paypalGateway = new Paypal();
PaymentGateway googlePayGateway = new GooglePay();
PaymentGateway stripeGateway = new StripeAdapter(new Stripe());

List<PaymentGateway> paymentGateways = new ArrayList<>();
paymentGateways.add(paypalGateway);
paymentGateways.add(googlePayGateway);
paymentGateways.add(stripeGateway);

for (PaymentGateway gateway : paymentGateways) {
//just for demo purposes 🤣
gateway.makePayment(100.0);
}
}
}

Key Points of the Adapter Design Pattern​

  1. Purpose: The Adapter Pattern allows you to integrate incompatible interfaces. It acts as a bridge between two incompatible interfaces by providing a way to use the existing functionality.

  2. Legacy Code Compatibility: It is particularly useful when dealing with legacy code that cannot be modified directly. Instead of changing the existing code, an adapter makes it compatible with new interfaces.

  3. Example: In our example, Stripe was using the charge() method, which wasn't compatible with our PaymentGateway interface that required makePayment(). By using an Adapter (StripeAdapter), we made them compatible.

Differences Between Adapter, Facade, and Proxy Patterns​

Let's test whether you learnt perfectly
  • Adapter: Use the Adapter Pattern when you need to make some existing class work with a new interface, especially when dealing with legacy code.

  • Facade: Use the Facade Pattern when you want to simplify interaction with a complex subsystem by providing a unified interface. It hides the complexity and provides a simpler interface to the client.

  • Proxy: Use the Proxy Pattern when you need to control access to an object. Proxies are used for implementing access control, lazy initialization, logging, etc.