Basics of Design Patterns — Adapter 🔌

Martin Jurran
Software Design Patterns
5 min readJun 14, 2024

Adapter is a Design Pattern that allows objects with incompatible Interfaces to interact with each other.

Real-World Analogy 🌍

The ability of using a power plug without adapters: Everyone is happy

Imagine that you are on a long-awaited trip to Japan. Suddenly, you need to charge one of your devices and get a surprise. The power plug and the sockets are different between some countries. That’s why your European plug won’t work in Japan.

The issue can be solved through using a Japanese-style adapter on top of your European-style plug.

Problem 🤔

We are creating a weather forecasting app for our trip. The app is utilizing external sources to display weather data to the user.

We started out with their provided library, which is supplying weather data in XML format.

We can’t directly send weather data to the analytics library as the data format is incompatible.

Now, we would like to calculate air quality metrics that aren’t provided by our existing service. The air quality analytics library only works with data in JSON format.

Solution 💡

An adapter is a handy tool that converts one object’s interface to another object’s interface.

It works by wrapping one of the objects to disguise the conversion process taking place behind the scenes. The other object that’s part of the conversion doesn’t even know there’s an adapter involved!

Here’s how it works: Your adapter will have an interface that’s compatible with one of your existing objects. Your existing object can then safely call the adapter’s methods. When the adapter receives a call, it’ll pass the request to the second object, but in a format and order that the second object expects.

Sometimes you can even create a two-way adapter that can convert the calls in both directions! With adapters, you can make even the most incompatible interfaces compatible.

To solve incompatible format issues with our weather app, create an XML-to-JSON adapter for the objects used with the Air Quality Analytics Library. Then, adjust the code to communicate solely through the adapter. The adapter will translate incoming XML data into a JSON structure and forward calls to the appropriate analytics object methods.

Structure 🏗️

1. The Client is where the business logic is stored.

2. The Interface describes properties and methods other classes must follow to collaborate with the Client.

3. The Object Adapter is able to work with both the Client and the Object through a joint Interface.

4. The Object is a third-party or legacy class that is to be used. The Client can’t call the class directly as it is using incompatible interfaces.

How to implement

An typical example of the usage of the Adapter Pattern would be the conversion between different units. In our example, we convert from actual scientific measurements into a human-readable QualityLevel format.

The Adapter pretends to serve AirQualityLevel in a human-readable format, but converts actual scientific values extracted from ExternalWeatherAnalytics.

  1. Identify a 3rd party library that is not compatible with your business logic.
  2. Identify which Interface is required for your business logic and which for your third-party library.
  3. Create an Adapter that implements the Interface required by the business logic.
  4. Implement the conversion logic within the Adapter. Sometimes, mapping can be handy.
  5. Connect everything together.
interface InterfaceWeatherAnalytics {
QualityLevel GetAirQualityLevel(Coordinates coords);
}


class ExternalWeatherAnalytics {
public double GetPM25ByCoord(Coordinates coords) { /.../ }
public double GetVOCByCoord(Coordinates coords) { /.../ }
public double GetPPMByCoord(Coordinates coords) { /.../ }
}


class WeatherAnalyticsAdapter : InterfaceWeatherAnalytics {
private ExternalWeatherAnalytics _target;


public WeatherAnalyticsAdapter(ExternalWeatherAnalytics target) {
_target = target;
}

public QualityLevel GetAirQualityLevel(Coordinates coords) {
double pm25 = _target.GetPM25ByCoord(coords);
double voc = _target.GetVOCByCoord(coords);
double ppm = _target.GetPPMByCoord(coords);

// Calculate QualityLevel based on pm25, voc, and ppm
return QualityLevel;
}

}


// Usage example
class Client {
static void Main(string[] args) {
ExternalWeatherAnalytics externalAPI = new ExternalWeatherAnalytics();
WeatherAnalyticsAdapter adapter = new WeatherAnalyticsAdapter(externalAPI);

Coordinates coords = new Coordinates(latitude, longitude);
QualityLevel airQuality = adapter.GetAirQualityLevel(coords);

// Use airQuality to make decisions in Business Logic
}
}

Pros and Cons

Pros

Single Responsibility Principle. Separate data conversion from business logic.

Open/Closed Principle. Introduce new adapters without changing the underlying code.

Cons

Might overcomplicate things. Sometimes, an easier approach might be suitable.

Might be the wrong approach. Sometimes, adapters might be handy to bridge the gap between poorly designed parts of a software. In such a case, Adapters should not be used.

Relation with other patterns

  • The Bridge is developed initially to enable independent development of different parts of an application. On the other hand, the Adapter Pattern is commonly used to integrate incompatible classes with an existing application.
  • The Adapter patterns creates a different interface for accessing an existing object, while the Decorator Pattern either maintains or expands the interface.
  • The Adapter pattern allows you to access an existing object through a different interface. The Proxy Pattern, however, maintains the same interface while providing additional functionality or control. And the
  • The Decorator pattern provides an enhanced interface for accessing the object, typically by adding new functionality to it.
  • The Facade Pattern creates a new interface for existing objects, while the Adapter Pattern attempts to make an existing interface usable by converting it to another interface.
  • The Bridge, State, Strategy and Adapter patterns share similar compositions, as each pattern delegates work to other objects. Nevertheless, all of these patterns are designed to solve distinct problems.

Attachments

Mermaid Diagram

//Structure
classDiagram
direction LR
class Client {
}
class Interface {
<<interface>>
+method(data)
}
class ObjectAdapter {
- target: Object
+method(data)
}
class Object {
+specialMethod(specialData)
}
Client -->Interface
ObjectAdapter..>Interface
ObjectAdapter-->Object
//Implementation
classDiagram
direction LR
class Client {
}
class InterfaceWeatherAnalytics {
<<interface>>
+GetAirQualityLevel(Coordinates) QualityLevel
}
class WeatherAnalyticsAdapter {
- _target: ExternalWeatherAnalytics
+GetAirQualityLevel(Coordinates) QualityLevel
}
class ExternalWeatherAnalytics {
+GetPM25ByCoord(Coordinates)
+GetVOCByCoord(Coordinates)
+GetPPMByCoord(Coordinates)
}
Client -->InterfaceWeatherAnalytics
WeatherAnalyticsAdapter..>InterfaceWeatherAnalytics
WeatherAnalyticsAdapter-->ExternalWeatherAnalytics

About this series

I noticed personally, that design patterns still remain a mistery to a lot of software engineers, even though these concepts are nothing new and have been around for decades.

I want to solve this problem with this article series to help other software engineers grow.

Feel free to comment in case you find mistakes or want to add your own perspective!

(Photo by the author, Illustrations by Takashi Mifune under free use)

--

--

Martin Jurran
Software Design Patterns

Personal blog of a Software Engineer | Azure DevOps, C#/.NET, JavaScript