The Adapter pattern is a structural design pattern that allows incompatible interfaces to work together. It is used when we have an existing system with a given interface, but the interface doesnβt match the one that a client expects. The adapter pattern acts as a bridge between two incompatible interfaces, allowing them to work together.
The adapter pattern consists of three key elements:
- The **Client**: The client is the object that needs to use the incompatible interface.
- The **Adaptee**: The adaptee is the object that provides the incompatible interface.
- The **Adapter**: The adapter is responsible for bridging the gap between the Client and the Adaptee by implementing the interface that the client expects and delegating calls to the Adaptee.
A simple example of the adapter pattern can be seen in a power adapter for international travel. When traveling to a foreign country, the electrical outlet in the hotel room may have a different shape or voltage than what is needed to charge your device. To solve this, you can use a power adapter that plugs into the foreign outlet, and then allows you to plug your device into the adapter. The power adapter acts as an adapter, converting the incompatible interface of the foreign outlet into the compatible interface that your device expects.
Another example of the adapter pattern can be seen in the use of third-party libraries. Suppose we already have an application that uses a specific library for data storage, and the library has a specific interface. However, a new version of the library introduces a new interface, which is not compatible with the existing application. In this case, we can create an adapter to bridge the gap between the old interface and the new one, allowing the application to continue using the old library without requiring any significant changes.
Here is a Java code example of the adapter pattern using the power adapter scenario:
// This is the Client
class Phone {
private PowerSource powerSource;
public void setPowerSource(PowerSource powerSource) {
this.powerSource = powerSource;
}
public void charge() {
int volts = powerSource.getVolts();
System.out.println("Charging phone with "+volts+" volts");
}
}
// This is the Adaptee
class ElectricalOutlet {
public int getVoltage() {
return 220;
}
}
// This is the Adapter
class PowerAdapter implements PowerSource {
private ElectricalOutlet outlet;
public PowerAdapter(ElectricalOutlet outlet) {
this.outlet = outlet;
}
public int getVolts() {
int voltage = outlet.getVoltage();
System.out.println("Converting "+voltage+" volts to 5 volts");
return 5;
}
}
// This is the expected interface for the Client
interface PowerSource {
int getVolts();
}
// Example usage of the adapter pattern
Phone phone = new Phone();
ElectricalOutlet outlet = new ElectricalOutlet();
PowerSource adapter = new PowerAdapter(outlet);
phone.setPowerSource(adapter);
phone.charge();
In the code example above, the Phone class is our client, which expects an interface that defines a method getVolts(). However, the ElectricalOutlet class provides a different interface with a method getVoltage(). To bridge this gap, we create a PowerAdapter class that implements the PowerSource interface, and its constructor takes an instance of ElectricalOutlet. The getVolts() method of the PowerAdapter class converts the voltage of the outlet to 5 volts, which is what the phone expects. Finally, we create an instance of the PowerAdapter class and pass it to the Phone object as its power source. The phone can now charge with the power adapter, which acts as an adapter between the incompatible interfaces of the phone and electrical outlet.