Adapter Pattern explained simply
Thilan Dissanayaka Software Architecture January 29, 2020

Adapter Pattern explained simply

Ever needed to connect two incompatible interfaces without changing their source code? That’s exactly where the Adapter Pattern shines!

The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It acts like a bridge between two different interfaces.

What is the Adapter Pattern?

At its core, the Adapter Pattern:

  • Converts the interface of a class into another interface the client expects.
  • Allows classes to work together that otherwise couldn’t due to incompatible interfaces.
  • Promotes code reusability and flexibility.

In simple words:

It’s like a translator who helps two people speaking different languages communicate!

Here’s how the pieces fit together. The client talks to the target interface, the adapter implements that interface and translates calls to the legacy service behind the scenes.

A Real-Life Analogy

Imagine you have a laptop charger with a US plug, but you’re traveling in Europe where sockets are different. You don’t throw away your charger – you use a power adapter!

Similarly, in software, instead of rewriting code, we adapt it.

A Real Example (Without Adapter)

This is something you run into all the time in enterprise systems. You’ve got a legacy payment service that only speaks XML. It’s been running in production for years and nobody wants to touch it.

public class LegacyPaymentService {
    public String submitPayment(String xmlData) {
        // Talks to some ancient SOAP endpoint internally
        System.out.println("Processing XML payment: " + xmlData);
        return "<response><status>OK</status><txnId>TXN-001</txnId></response>";
    }
}

Meanwhile, your new checkout system expects every payment gateway to follow a clean, modern interface:

public class PaymentRequest {
    private String merchantId;
    private double amount;
    private String currency;
    private String cardToken;

    // constructors, getters, setters
}

public class PaymentResponse {
    private boolean success;
    private String transactionId;

    // constructors, getters, setters
}

public interface ModernPaymentGateway {
    PaymentResponse processPayment(PaymentRequest request);
}

Problem: LegacyPaymentService doesn’t implement ModernPaymentGateway. It wants XML strings, not PaymentRequest objects. You can’t just plug it into your new system.

What’s happening here? Two perfectly working pieces of code that can’t talk to each other. The legacy service isn’t broken. Your new interface isn’t wrong. They just speak different languages.

Enter the Adapter Pattern

We create a PaymentAdapter that implements the ModernPaymentGateway interface and wraps the LegacyPaymentService internally. The adapter handles all the messy conversion between JSON-style objects and XML strings.

public class PaymentAdapter implements ModernPaymentGateway {
    private LegacyPaymentService legacyService;

    public PaymentAdapter(LegacyPaymentService legacyService) {
        this.legacyService = legacyService;
    }

    @Override
    public PaymentResponse processPayment(PaymentRequest request) {
        // Convert the modern PaymentRequest into the XML format
        // the legacy service expects
        String xml = "<payment>"
                + "<merchantId>" + request.getMerchantId() + "</merchantId>"
                + "<amount>" + request.getAmount() + "</amount>"
                + "<currency>" + request.getCurrency() + "</currency>"
                + "<cardToken>" + request.getCardToken() + "</cardToken>"
                + "</payment>";

        // Delegate to the legacy service
        String xmlResponse = legacyService.submitPayment(xml);

        // Parse the XML response back into our modern object
        PaymentResponse response = new PaymentResponse();
        response.setSuccess(xmlResponse.contains("<status>OK</status>"));
        response.setTransactionId(
                xmlResponse.replaceAll(".*<txnId>(.*)</txnId>.*", "$1")
        );

        return response;
    }
}

Now the rest of your codebase never has to know that a legacy XML service is lurking underneath. Everything just works through the clean ModernPaymentGateway interface.

public class CheckoutService {
    public static void main(String[] args) {
        // Wire up the legacy service behind the adapter
        LegacyPaymentService legacyService = new LegacyPaymentService();
        ModernPaymentGateway gateway = new PaymentAdapter(legacyService);

        // Client code only sees the modern interface
        PaymentRequest request = new PaymentRequest("MERCH-42", 99.99, "USD", "tok_abc123");
        PaymentResponse response = gateway.processPayment(request);

        System.out.println("Payment success: " + response.isSuccess());
        System.out.println("Transaction ID: " + response.getTransactionId());
    }
}

Output:

Processing XML payment: <payment><merchantId>MERCH-42</merchantId><amount>99.99</amount><currency>USD</currency><cardToken>tok_abc123</cardToken></payment>
Payment success: true
Transaction ID: TXN-001

The checkout service doesn’t care about XML. It doesn’t know about SOAP. It just calls processPayment() and gets back a nice PaymentResponse. That’s the whole point.

Key Components

  • Target Interface (ModernPaymentGateway): The interface your client code expects.
  • Adaptee (LegacyPaymentService): The existing class that needs adapting.
  • Adapter (PaymentAdapter): Bridges the gap between the Target and Adaptee, handling all the format conversion.

Types of Adapter Pattern

There are mainly two ways to implement Adapter Pattern:

1. Class Adapter (using Inheritance)

  • Adapter extends Adaptee and implements the Target interface.
  • Not very flexible because Java supports single inheritance only.

2. Object Adapter (using Composition) – Preferred

  • Adapter has an instance of Adaptee.
  • More flexible and preferred in most cases.

Note: The payment example above is an Object Adapter. The PaymentAdapter holds a reference to LegacyPaymentService rather than extending it.

When to Use the Adapter Pattern?

  • When you want to use an existing class but its interface doesn’t match your needs.
  • When you want to create a reusable class that cooperates with unrelated classes.
  • When you need to work with legacy code without modifying it.

Real World Use Cases

  • Legacy system integration: Adapting old XML/SOAP APIs to work with modern REST/JSON services, like the payment example above.
  • Third-party library integration: Adapting library classes to your own interfaces.
  • UI component libraries: Adapting different UI components under a single standard.

Advantages

  • Promotes code reuse.
  • Makes incompatible classes work together.
  • Follows the Open/Closed Principle – open for extension but closed for modification.

Disadvantages

  • Increases code complexity due to additional classes.
  • Overuse can lead to too many adapters, making the code harder to maintain.

Final Thoughts

The Adapter Pattern is like a universal connector in software development. It allows your systems to evolve without needing risky and expensive rewrites.

Whenever you encounter a mismatch between interfaces – like a legacy XML payment service that needs to play nice with your modern checkout system – think:

“Can I just adapt it instead of rewriting it?”

Happy coding!

ALSO READ
Blockchain 0x000 – Understanding the Fundamentals
May 21, 2020 Web3 Development

Imagine a world where strangers can exchange money, share data, or execute agreements without ever needing to trust a central authority. No banks, no intermediaries, no single point of failure yet...

Identity and Access Management (IAM)
May 11, 2020 Identity & Access Management

Who are you — and what are you allowed to do? That's the fundamental question every secure system must answer. And it's exactly what Identity and Access Management (IAM) is built to solve.

How I built a web based CPU Simulator
May 07, 2020 Pet Projects

As someone passionate about computer engineering, reverse engineering, and system internals, I've always been fascinated by what happens "under the hood" of a computer. This curiosity led me to...

Writing a Shell Code for Linux
Apr 21, 2020 Exploit Development

Shellcode is a small piece of machine code used as the payload in exploit development. In this post, we write Linux shellcode from scratch — starting with a simple exit, building up to spawning a shell, and explaining every decision along the way.

Exploiting a Stack Buffer Overflow on Windows
Apr 12, 2020 Exploit Development

In a previous tutorial we discusses how we can exploit a buffer overflow vulnerability on a Linux machine. I wen through all theories in depth and explained each step. Now today we are going to jump...

Access Control Models
Apr 08, 2020 Identity & Access Management

Access control is one of the most fundamental concepts in security. Every time you set file permissions, assign user roles, or restrict access to a resource, you're implementing some form of access control. But not all access control is created equal...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01, 2020 Exploit Development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....

Basic concepts of Cryptography
Mar 01, 2020 Cryptography

Ever notice that little padlock icon in your browser's address bar? That's cryptography working silently in the background, protecting everything you do online. Whether you're sending an email,...

Common Web Application Attacks
Feb 05, 2020 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Remote Code Execution (RCE)
Jan 02, 2020 Application Security

Remote Code Execution (RCE) is the holy grail of application security vulnerabilities. It allows an attacker to execute arbitrary code on a remote server — and the consequences are as bad as it sounds. In this post, we'll go deep into RCE across multiple languages, including PHP, Java, Python, and Node.js.