Skip to content

Design Pattern: Adapter (Structural)

Make an incompatible interface fit

Builds on Interfaces and Composition vs Inheritance

Stage 3core2 min read

The Adapter Pattern Solution

The refactored code uses the Adapter Pattern (also called the Wrapper Pattern). The new RazorpayAdapter closes the gap between our own PaymentGateway interface and the incompatible third-party RazorpayAPI.


1. Key Improvements

Open/Closed Principle (OCP)

  • The Benefit: We added a new payment provider without editing the existing CheckoutService code.
  • The Impact: The system is open for extension (add new adapters) but closed for modification (the core business logic stays untouched).

Translation Encapsulation

  • The Benefit: The messy details of mapping orderId to invoiceId and pay() to makePayment() are hidden inside the Adapter.
  • The Impact: CheckoutService stays "clean". It only uses its own words, such as orderId and pay().

Single Responsibility

  • The Benefit: The RazorpayAdapter has one job: converting one interface to another.
  • The Impact: This keeps vendor-specific logic and data conversions out of CheckoutService.

2. Structural Breakdown

ComponentResponsibility
Target (PaymentGateway)The interface our own code defines and the client uses.
Client (CheckoutService)Works with any object that follows the Target interface.
Adaptee (RazorpayAPI)The existing class that needs adapting (the third-party code).
Adapter (RazorpayAdapter)Wraps the Adaptee so it looks like the Target.

3. Comparison of "Before" vs "After"

FeatureBefore (Incompatible)After (Adapter Pattern)
InteroperabilityCheckoutService cannot use RazorpayAPI.CheckoutService uses RazorpayAPI via the adapter.
Client ChangesRequired to support new APIs.Zero changes required to the client.
Third-Party CodeMight require modification (often impossible).Used as-is, wrapped by the adapter.
MaintenanceHigh risk when switching vendors.Low risk; just swap the adapter instance.

4. Summary of the "Adapter"

The Adapter Pattern is a translator. Just as a travel adapter lets an Indian plug work in a European socket, the RazorpayAdapter allows the RazorpayAPI to "plug in" to the CheckoutService.

In this topic

After code1 file

After/Main.java98 linesGitHub
package DesignPatterns.StructuralPatterns.AdapterDesignPattern.After;/** * 1. TARGET INTERFACE * This defines the "domain-specific" interface that the Client (CheckoutService) uses. */interface PaymentGateway {    void pay(String orderId, double amount);}/** * 2. COMPATIBLE IMPLEMENTATION * A standard implementation that already follows our internal contract. */class PayUGateway implements PaymentGateway {    @Override    public void pay(String orderId, double amount) {        System.out.println(            "Paid Rs. " + amount + " using PayU for order: " + orderId        );    }}/** * 3. ADAPTEE (The External Legacy/Third-Party System) * This class has the functionality we need, but its interface is incompatible * with our PaymentGateway (different method names/parameters). */class RazorpayAPI {    public void makePayment(String invoiceId, double amountInRupees) {        System.out.println(            "Paid Rs. " +                amountInRupees +                " using Razorpay for invoice: " +                invoiceId        );    }}/** * 4. THE ADAPTER * This is the "Wrapper." It implements the Target interface and delegates * the work to the Adaptee (RazorpayAPI). */class RazorpayAdapter implements PaymentGateway {    private RazorpayAPI razorpayAPI;    public RazorpayAdapter() {        this.razorpayAPI = new RazorpayAPI();    }    /**     * TRANSLATION LAYER:     * This method maps our standard 'pay()' call to the third-party 'makePayment()' call.     */    @Override    public void pay(String orderId, double amount) {        // We translate orderId -> invoiceId and amount -> amountInRupees        razorpayAPI.makePayment(orderId, amount);    }}/** * 5. CLIENT * Stays 100% decoupled. It doesn't know (or care) if it's talking to * PayU directly or to Razorpay via an Adapter. */class CheckoutService {    private PaymentGateway paymentGateway;    public CheckoutService(PaymentGateway paymentGateway) {        this.paymentGateway = paymentGateway;    }    public void checkout(String orderId, double amount) {        paymentGateway.pay(orderId, amount);    }}class Main {    public static void main(String[] args) {        // SUCCESS: We can now plug the 'Adapter' into the 'Service'.        // This follows the Open/Closed Principle: We added Razorpay        // without modifying the CheckoutService class.        CheckoutService checkoutService = new CheckoutService(            new RazorpayAdapter()        );        checkoutService.checkout("12", 1780);    }}

Done with this topic?

How well do you know it?

PreviousCreational Design Patterns: Master Comparison