-
Client Interface: The existing system expects data in a specific format (e.g., JSON).
-
Service Interface: The third-party or legacy system provides data in an incompatible format (e.g., XML).
-
Adapter: The adapter implements the client interface while wrapping the service object, converting incoming data to the required format.
-
Interaction: The client interacts with the adapter, unaware of the conversion complexity.
This separation ensures that changes in the service interface don't ripple through the client code.
Real-World Analogy
Imagine traveling from the U.S. to Europe and trying to charge your laptop. The power outlets in Europe differ from those in the U.S. A power plug adapter bridges this gap by converting the U.S. plug to fit European sockets. Similarly, in software, the Adapter pattern converts one interface to work seamlessly with another.
Types of Adapters
Object Adapter: Utilizes object composition by wrapping the service object within the adapter class. This is more flexible since it can work with multiple service objects.
Class Adapter: Uses inheritance to combine the interfaces of both the client and service classes. This approach is limited to languages that support multiple inheritance, such as C++.
Pseudocode Example
To illustrate, consider adapting square pegs to fit round holes:
class RoundHole {
private double radius;
public RoundHole(double radius) { this.radius = radius; }
public double getRadius() { return radius; }
public boolean fits(RoundPeg peg) {
return this.radius >= peg.getRadius();
}
}
class SquarePeg {
private double width;
public SquarePeg(double width) { this.width = width; }
public double getWidth() { return width; }
}
class SquarePegAdapter extends RoundPeg {
private SquarePeg peg;
public SquarePegAdapter(SquarePeg peg) { this.peg = peg; }
@Override
public double getRadius() {
return (peg.getWidth() * Math.sqrt(2)) / 2;
}
}
In this example, the adapter makes the square peg behave like a round peg by calculating a compatible radius.
When to Use the Adapter Pattern
- When you need to integrate a legacy system or third-party library with incompatible interfaces.
- When extending every subclass to add missing functionality is inefficient.
- When maintaining flexibility and scalability is crucial.
Pros and Cons
✅ Pros:
✔ Adheres to the Single Responsibility Principle by separating conversion logic.
✔ Promotes the Open/Closed Principle, allowing new adapters without breaking existing code.
✔ Enables code reuse and scalability.
❌ Cons:
✘ Increases overall system complexity.
✘ May introduce performance overhead due to extra layers of abstraction.
Adapter vs. Other Patterns
-
Facade: Defines a new interface, while the Adapter makes an existing interface usable.
-
Decorator: Extends functionality while keeping the interface unchanged.
-
Proxy: Provides a surrogate for another object without altering the interface.
Conclusion
The Adapter design pattern is essential for integrating incompatible systems without extensive modifications. By introducing a translation layer, it preserves existing functionality while enabling seamless collaboration with new or legacy components. Whether dealing with legacy systems, third-party libraries, or simply different data formats, the Adapter pattern offers a clean and scalable solution.