Skip to content

Adapter Design Pattern (Structural)

Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.

Imagine that you’re creating a stock market monitoring app. The app downloads the stock data from multiple sources in XML format and then displays nice-looking charts and diagrams for the user.

At some point, you decide to improve the app by integrating a smart 3rd-party analytics library. But there’s a catch: the analytics library only works with data in JSON format.

You could change the library to support XML. However, this might break some existing code that relies on the library. And worse, you might not have access to the library’s source code in the first place, making this approach impossible.

You can create an adapter. This is a special object that converts the interface of one object so that another object can understand it.

An adapter wraps one of the objects to hide the complexity of conversion happening behind the scenes. The wrapped object isn’t even aware of the adapter. For example, you can wrap an object that operates in meters and kilometers with an adapter that converts all of the data to imperial units such as feet and miles.

Adapters can not only convert data into various formats but can also help objects with different interfaces collaborate. Here’s how it works:

  1. The adapter gets an interface, compatible with one of the existing objects.
  2. Using this interface, the existing object can safely call the adapter’s methods.
  3. Upon receiving a call, the adapter passes the request to the second object, but in a format and order that the second object expects.

Sometimes it’s even possible to create a two-way adapter that can convert the calls in both directions.

When you travel from Europe to the US for the first time, you might get a surprise when trying to charge your laptop. The power plug and sockets standards are different in different countries. That’s why your European plug won’t fit a US socket. The problem can be solved by using a power plug adapter that has the American-style socket and the European-style plug.

classDiagram
    class Client {
    }
    class ClientInterface {
        <<interface>>
        +method(data)
    }
    class Adapter {
        -adaptee: Adaptee
        +method(data)
    }
    class Adaptee {
        +specificRequest(specialData)
    }

    Client --> ClientInterface
    Adapter ..|> ClientInterface
    Adapter --> Adaptee : wraps/delegates
# The Client Interface describes a protocol that other
# classes must follow to be able to work with the client code.
interface RoundPeg:
method getRadius(): int
# A class that you want to make compatible with your code.
class SquarePeg:
field width: int
constructor SquarePeg(width):
this.width = width
method getWidth(): int
return this.width
# The Adapter allows using square pegs in round holes.
class SquarePegAdapter implements RoundPeg:
private peg: SquarePeg
constructor SquarePegAdapter(peg: SquarePeg):
this.peg = peg
method getRadius(): int
# The adapter pretends that it's a round peg with a
# radius that can fit the square peg that the adapter
# actually wraps.
return this.peg.getWidth() * sqrt(2) / 2
  • Use the Adapter class when you want to use some existing class, but its interface isn’t compatible with the rest of your code.
  • Use the pattern when you want to reuse several existing subclasses that lack some common functionality that can’t be added to the superclass.
  1. Ensure that you have at least two classes with incompatible interfaces:
    • A useful Service (usually 3rd-party or legacy) which you can’t change.
    • A Client that would benefit from using the service class.
  2. Declare the client interface and describe how the client communicates with the service.
  3. Create the adapter class and make it follow the client interface. Leave all methods empty for now.
  4. Add a field to the adapter class to store a reference to the service object.
  5. One by one, implement all methods of the client interface in the adapter class. The adapter should delegate most of the real work to the service object, handling only the interface or data format conversion.
  6. Clients should use the adapter via the client interface. This will let you change or extend the adapters without affecting the client code.

Pros:

  • Single Responsibility Principle. You can separate the interface or data conversion code from the primary business logic of the program.
  • Open/Closed Principle. You can introduce new types of adapters into the program without breaking the existing client code, as long as they work with the client interface.

Cons:

  • The overall complexity of the code increases because you need to introduce a set of new interfaces and classes. Sometimes it’s simpler just to change the service class so that it fits the rest of your code.
  • Adapter changes the interface of an existing object, while Decorator enhances an object without changing its interface.
  • Adapter provides a different interface to its subject. Proxy provides it with the same interface. Decorator provides it with an enhanced interface.
  • Bridge is usually designed up-front, letting you develop parts of an application independently of each other. Adapter is commonly used with an existing app to make some otherwise-incompatible classes work together nicely.

// Interface expected by the client
public interface ITarget
{
void Request();
}
// Existing class with incompatible interface.
public class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Call for the specific Adaptee method.");
}
}
// Adapter
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
_adaptee = adaptee;
}
public void Request()
{
_adaptee.SpecificRequest();
}
}
// Usage
class Program
{
static void Main()
{
var adaptee = new Adaptee();
ITarget target = new Adapter(adaptee);
target.Request();
}
}