The Adapter Design Pattern

Ever tried to plug an American hair dryer into a European socket? It doesn’t fit. That’s frustrating. But grab a travel adapter, and voila! Your hair dryer is back in action. This is a perfect analogy for the Adapter Pattern in software development. Read along to learn more about what the Adapter design pattern is, and when to use it.

What Is the Adapter Pattern?

In software engineering, the Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It’s like hiring a translator (or even better a Babel Fish) who knows two languages fluently and makes sure both parties in the conversation understand each other. In coding terms, it allows one class to adapt to the interface expected by another class, without changing either of them. It enables classes that weren’t designed to work together and communicate seamlessly.

You can pull up the diagram below by going to planttext.com, clicking Samples, then selecting Design Patterns > Adapter.

Basic Adapter Design Pattern

@startuml

class Client

interface Target {
  + request(): void
}

class Adapter implements Target {
  - adaptee: Adaptee
  + Adapter(adaptee: Adaptee)
  + request(): void
}

class Adaptee {
  + specificRequest(): void
}

Client ..> Target
Adapter o-- Adaptee

@enduml

Why Bother with It?

Good question! Why not just make everything compatible from the start, right? If only we had that kind of foresight, or better yet, some magical crystal ball to predict the future where legacy systems and third-party libraries miraculously play nice with the new shiny code we’re writing. But alas, the cruel truth of software development is that we don’t. The code we inherit is often older, more tangled, and held together with duct tape and dreams. And that third-party library? Well, let’s just say it’s “no longer supported” but somehow still part of your project. Enter the Adapter Pattern, your bittersweet salvation in this hellscape of incompatible systems. It’s the only thing standing between you and the abyss of rewrite nightmares. Here’s why the Adapter design pattern is the weary traveler’s lifeline:

  1. Reusability: Instead of rewriting existing code to make it compatible with your system, you can create an adapter that wraps around it and makes it usable.
  2. Maintainability: You won’t need to alter the existing code, meaning fewer bugs and fewer headaches.
  3. Flexibility: You can integrate third-party libraries or APIs without having to change their code—just create an adapter!

It’s all about making life easier by avoiding unnecessary refactoring while keeping your codebase clean and easy to maintain.

When Should You Use the Adapter Pattern?

There are plenty of scenarios where the Adapter design pattern comes in handy, but here are the top ones:

  • You need to use an existing class with an incompatible interface. Imagine a scenario where you have a modern API that provides JSON responses, but your old system only understands XML. Instead of modifying the API or rewriting the legacy system, you can create an adapter to convert JSON to XML on the fly.
  • You want to reuse a class but can’t modify it. Maybe it’s from a third-party library, or maybe touching the class would break everything. Instead of crying over it, use an adapter.
  • You’re working with multiple systems that have different ways of communicating. Need to unify how different subsystems interact? An adapter can provide a consistent interface for your entire application.

How to Use the Adapter Pattern

The Adapter Pattern can be implemented in two ways, but just do the Object Adapter for crying out loud! I’m so over people using inheritance all over the place.

  1. Object Adapter (Using Composition): This is the most commonly used. It uses simple composition and the adapter contains an instance of the adaptee and delegates method calls. It’s flexible, doesn’t over-complicate things, and actually works. If you’re adapting legacy code, this is your go-to move.
  2. Class Adapter (Using Inheritance): This approach uses multiple inheritance (which some languages like Java don’t support directly). The adapter class inherits from both the target interface and the adaptee class, bridging the gap between them…because why not make things harder?

I almost left the Class Adapter out because it’s almost the same thing. I’ve included it for completeness. But I prefer to keep things simple.

Let’s look at an example of an Adapter in use to make it crystal clear.

Example: Making Old Code Work with New Systems

Let’s say you’ve got a speed monitoring system that’s built to measure vehicle speed in miles per hour (MPH). Unfortunately, your speedometer only outputs speed in kilometers per hour (KPH). What do you do? Rewrite everything to support MPH? That would be a headache. Enter the Adapter Pattern to save the day.

Speedometer Adapter Pattern Example

@startuml

' Client
class SpeedMonitorSystem

' Target
interface IVehicleSpeed {
  + retrieveSpeedInMilesPerHour(): double
}

' Adapter
class KphToMphConverter implements IVehicleSpeed {
  - kphSpeedometer: KphSpeedometer
  + KphToMphConverter(kphSpeedometer: KphSpeedometer)
  + retrieveSpeedInMilesPerHour(): double
}

'Adaptee
class KphSpeedometer {
  + getSpeedInKilometers(): double
}

' Relationships
SpeedMonitorSystem ..> IVehicleSpeed
KphToMphConverter o-- KphSpeedometer

@enduml

The alternative to using the Adapter design pattern in this situation would be to modify the existing KphSpeedometer class directly to return the speed in miles per hour (MPH) instead of kilometers per hour (KPH). This would eliminate the need for an adapter, but it comes with its own set of issues. Maybe that causes breaking changes because another part of the system is currently using it. This could also add more tight coupling and reduce flexibility. So, let’s just keep going using the Adapter Pattern. Ok, so what are the steps you need to take…

using System;

#region Target Interface
public interface IVehicleSpeed
{
    double RetrieveSpeedInMilesPerHour();
}
#endregion

#region Adaptee (Existing Incompatible Class)
public class KphSpeedometer
{
    public double GetSpeedInKilometers()
    {
        // Simulating getting speed in KPH
        return 100.0; // Example speed in kilometers per hour
    }
}
#endregion

#region Adapter Class
public class KphToMphConverter : IVehicleSpeed
{
    private KphSpeedometer _kphSpeedometer;

    public KphToMphConverter(KphSpeedometer kphSpeedometer)
    {
        _kphSpeedometer = kphSpeedometer;
    }

    public double RetrieveSpeedInMilesPerHour()
    {
        double kph = _kphSpeedometer.GetSpeedInKilometers(); // Get speed in KPH
        return kph * 0.621371; // Convert KPH to MPH
    }
}
#endregion

#region Client Class
public class SpeedMonitorSystem
{
    private IVehicleSpeed _vehicleSpeed;

    public SpeedMonitorSystem(IVehicleSpeed vehicleSpeed)
    {
        _vehicleSpeed = vehicleSpeed;
    }

    public void DisplaySpeed()
    {
        double speed = _vehicleSpeed.RetrieveSpeedInMilesPerHour();
        Console.WriteLine($"The vehicle is moving at {speed} MPH.");
    }
}
#endregion

#region Main Execution
class Program
{
    static void Main()
    {
        // Existing system uses KPH
        KphSpeedometer kphSpeedometer = new KphSpeedometer();

        // Adapting KPH to MPH
        IVehicleSpeed vehicleSpeed = new KphToMphConverter(kphSpeedometer);

        // Client system that expects MPH
        SpeedMonitorSystem monitor = new SpeedMonitorSystem(vehicleSpeed);
        monitor.DisplaySpeed(); // Output: The vehicle is moving at 62.1371 MPH.
    }
}
#endregion

1. Understand the Adaptee Class

This is the legacy, or incompatible class. It provides a method getSpeedInKilometers() that retrieves the speed in kilometers per hour, but this isn’t what we need for our new system.

First just make sure you know what it does and how it does it. And, of course, make sure you know how you want it to work in your system.

2. Define the Target Interface

This is the target interface that the SpeedMonitorSystem expects. It defines a method retrieveSpeedInMilesPerHour() to fetch the speed in miles per hour.

3. Create the Adapter Class

The hero of our story. It implements the IVehicleSpeed interface and contains an instance of KphSpeedometer. It then adapts the getSpeedInKilometers() method by converting the value into miles per hour using a simple formula.

4. The Client Code

Now, the client (SpeedMonitorSystem) can use the KphToMphConverter to adapt the KphSpeedometer to the expected interface (IVehicleSpeed). The client doesn’t need to worry about whether the speedometer is returning KPH or MPH—it just calls the retrieveSpeedInMilesPerHour() method, and the adapter takes care of the rest.

5. Putting It All Together

Sure, if you really want to get technical about it, yes, this example is simple, and you could probably get away with just directly calling the KphSpeedometer in your SpeedMonitorSystem. After all, it’s just a straightforward conversion from KPH to MPH, so why bother with the extra class?

But hey, if you’re into creating more flexible, maintainable code (or if you just want to impress your colleagues with your fancy design patterns), then the Adapter Pattern has its place. It’s not a big deal if you skip it here, but as systems grow more complex, you’ll thank yourself later when you’re not rewriting everything just to accommodate a new speedometer. But hey, no pressure.

The Adapter Pattern: Making Square Pegs Fit into Round Holes

The Adapter design pattern allows you to connect and control systems that weren’t designed to work together. It allows you to integrate incompatible classes or systems without changing their core logic. Whether you’re dealing with legacy systems, third-party libraries, or mismatched interfaces, the Adapter Pattern can save you from major headaches.

So, next time you find yourself trying to jam a square peg into a round hole in your codebase, don’t force it—just build an adapter! It’s your trusty tool to make those square pegs fit into round holes with minimal fuss. Happy coding! 🚀

For more on design patterns, check out the other articles in this series.

Scroll to Top