Introduction

In real projects, you often need to connect two pieces of code that were designed differently:

  • Your system expects a PaymentProcessor with a process(amount) method.

  • A third‑party library exposes LegacyPayment with makePayment(value, currency).

You don’t want to rewrite the library, and you don’t want to change the rest of your system.
The Adapter Design Pattern solves this by creating a wrapper that looks like the interface your system expects but uses the existing class internally.

This article explains the Adapter in simple terms, shows a clear C++ example, and describes when this pattern is useful in Low-Level Design.


Intent of the Adapter pattern

The Adapter pattern has one core purpose:

Convert the interface of a class into another interface clients expect, so classes with incompatible interfaces can work together.

Key ideas:

  • The client knows and uses a target interface (what it expects).

  • There is an adaptee (existing class) with a different interface.

  • The adapter implements the target interface and internally delegates to the adaptee, translating calls and data as needed.

You can think of it like a plug adapter:

  • The wall socket is one shape.

  • Your device plug is another shape.

  • The physical adapter lets them connect without changing either the wall or the device.


Basic structure: Target, Adaptee, Adapter

Conceptually, you have:

  • Target: the interface your code already expects.

  • Adaptee: the existing class you want to reuse.

  • Adapter: a class that implements Target and uses Adaptee inside.

Example: Modern audio player vs legacy player

Suppose your system expects an AudioPlayer interface:


You have legacy code:


Your new code uses AudioPlayer*, but the library gives you LegacyPlayer.
Their method names and interfaces don’t match.

Implementing the Adapter


Usage:


Here:

  • The client only knows AudioPlayer::play.

  • The adapter implements AudioPlayer and delegates to LegacyPlayer::playFile.

  • LegacyPlayer remains unchanged.


Why use an Adapter instead of modifying classes?

The Adapter pattern is valuable when:

  • You cannot change the existing class:

    • It is part of a third‑party library.

    • It is used in many other places, and changing its interface would break other code.

  • You want to avoid breaking existing clients:

    • The rest of your system already depends on a particular interface (target).

    • You introduce a new implementation that doesn’t match, so you adapt it.

Benefits: Low-risk integration

    • The adapter is a small, focused piece of code that connects old and new parts.

  • No invasive changes

    • You don’t rewrite the legacy or third‑party code.

    • You don’t force all clients to change how they call methods.

  • Clear separation

    • The adapter becomes a clear “boundary” between two modules or systems.


Another example: Payment integration

Imagine your application expects:


You now need to integrate a gateway with this interface:

Instead of changing all your code to know about makePaymentWrite an adapter:


Usage:


Your business logic still works with PaymentProcessor.
Only the adapter knows aboutLegacyPaymentGateway, keeping integration clean.


Object adapter vs class adapter

There are two common flavors:

  1. Object adapter (most common, shown above)

    • The adapter has an instance of the adaptee (composition).

    • Easier to change at runtime (you can swap the adaptee instance).

    • Works well in languages like C++/Java where multiple inheritance is limited or complex.

  2. Class adapter (less common)

    • The adapter inherits from both the target and the adaptee.

    • Uses multiple inheritance to adapt behavior.

    • Tightly couples the adapter to the adaptee’s implementation.

In modern practice, the object adapter style (composition) is preferred because it is more flexible and better aligned with “favor composition over inheritance.”


When to use the Adapter pattern (and when not to)

Use Adapter when:

  • You have existing code or libraries with interfaces that don’t match your current design.

  • You want to reuse those classes without changing them.

  • You need to keep your client code simple and dependent on a clean target interface.

Avoid or rethink the Adapter when:

  • You control all the code and can easily refactor the interfaces.

  • The mismatch is minor and can be fixed by directly updating method signatures.

  • The adapter would become very complex, indicating that deeper redesign is needed.

As always, follow KISS and YAGNI:

  • Don’t wrap everything in adapters by default.

  • Introduce adapters where they clearly reduce pain and keep boundaries clean.


Summary

The Adapter Design Pattern is a structural pattern that:

  • Let classes with incompatible interfaces work together without changing them.

  • Introduces an adapter that implements the interface clients expect (target) and delegates calls to an existing class (adaptee).

  • Often uses composition to hold a reference to the adaptee and translate calls and data.

You saw:

  • The core idea: “make two mismatched interfaces talk to each other through a wrapper.”

  • A simple AudioPlayer/LegacyPlayer example.

  • A practical payment integration example.

  • The difference between object adapters and class adapters, and why composition is usually preferred.

  • When adapters are helpful in Low Level Design, especially for integrating legacy or third‑party code.