The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It acts as a wrapper that converts the interface of one class into an interface that a client expects.
The Problem
Imagine you are building a Weather Monitoring Dashboard for your website. Your system is designed to work with data in JSON format. However, you decide to integrate a new, high-end Weather Sensor API, but it only provides data in XML format.
You cannot change the new API (it’s a third-party library), and changing your entire dashboard to support XML would be a massive, buggy task.
The Solution: The "Translator"
You create an Adapter. This is a special object that sits between your Dashboard and the Sensor API. It takes the XML data from the sensor, converts it to JSON, and passes it to your dashboard. Your dashboard thinks it’s talking to a JSON source, and the sensor thinks it’s talking to an XML client.
Step-by-Step Java Implementation
1. The Target Interface
This is the interface your application (the client) uses.
// JsonDataDisplay.java
public interface JsonDataDisplay {
void displayData(String jsonString);
}
2. The Adaptee (The Incompatible Service)
This is the third-party class that has a useful function but an incompatible interface.
// XmlWeatherSensor.java
public class XmlWeatherSensor {
public String getXmlData() {
return "<weather><temp>25</temp><status>Sunny</status></weather>";
}
}
3. The Adapter
The Adapter implements the Target interface and holds a reference to the Adaptee. It "translates" the call.
// WeatherAdapter.java
public class WeatherAdapter implements JsonDataDisplay {
private XmlWeatherSensor xmlSensor;
public WeatherAdapter(XmlWeatherSensor sensor) {
this.xmlSensor = sensor;
}
@Override
public void displayData(String jsonString) {
// Step 1: Get data from the incompatible XML sensor
String xml = xmlSensor.getXmlData();
// Step 2: Logic to convert XML to JSON (Simplified for example)
System.out.println("Converting XML: " + xml + " to JSON...");
String convertedJson = "{ \"temp\": 25, \"status\": \"Sunny\" }";
// Step 3: Use the converted data
System.out.println("Dashboard displaying: " + convertedJson);
}
}
Full Code for Testing
This test shows how the client can use the "incompatible" sensor via the adapter.
// Save as AdapterTest.java
// 1. Existing Interface (Target)
interface JsonDataDisplay {
void requestData();
}
// 2. Third-Party Class (Adaptee)
class XmlWeatherSensor {
public String getRawXml() {
return "<data>Temperature: 25C</data>";
}
}
// 3. The Adapter
class WeatherAdapter implements JsonDataDisplay {
private XmlWeatherSensor sensor;
public WeatherAdapter(XmlWeatherSensor sensor) {
this.sensor = sensor;
}
@Override
public void requestData() {
String xml = sensor.getRawXml();
// Transformation logic happens inside the adapter
String json = "{ \"info\": \"" + xml.replaceAll("<[^>]*>", "") + "\" }";
System.out.println("Adapter converted XML to JSON: " + json);
}
}
// 4. Client Code
public class AdapterTest {
public static void main(String[] args) {
System.out.println("--- Adapter Pattern Test ---\n");
// The client wants to use the sensor but only speaks 'JsonDataDisplay'
XmlWeatherSensor legacySensor = new XmlWeatherSensor();
JsonDataDisplay adapter = new WeatherAdapter(legacySensor);
System.out.println("Client: I need data, but I only understand JSON.");
adapter.requestData();
System.out.println("\n--- Integration Successful ---");
}
}
Why use Adapter?
- Reusability: You can use existing classes even if their interfaces don't match your new code.
- Open/Closed Principle: You can introduce new adapters into the program without breaking existing client code.
- Separation of Concerns: You keep the complex data conversion logic out of your primary business logic.
Real-World Examples:
- Java IO:
InputStreamReader(InputStream)acts as an adapter between byte streams and character streams. - Database Drivers: Different JDBC drivers act as adapters between the standard Java SQL interface and specific database engines (MySQL, Oracle, etc.).