🔌 Adapter
Adapter Pattern that allows objects with incompatible Interfaces to interact with each other.
🌍 Real-World Analogy
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.
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
- Client is where the business logic is stored.
- Interface describes properties and methods other classes must follow to collaborate with the Client.
- Object Adapter is able to work with both the Client and the Object through a joint Interface.
- 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.
- Identify a 3rd party library that is not compatible with your business logic.
- Identify which Interface is required for your business logic and which for your third-party library.
- Create an Adapter that implements the Interface required by the business logic.
- Implement the conversion logic within the Adapter. Sometimes, mapping can be handy.
- 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 & 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
- 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.
- Adapter patterns creates a different interface for accessing an existing object, while the Decorator Pattern either maintains or expands the interface.
- 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
- Decorator pattern provides an enhanced interface for accessing the object, typically by adding new functionality to it.
- 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.
- 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.