The Adapter design pattern works when you have different or incompatible interfaces, and you need the client to use both in the same way.
The Adapter design pattern says in its definition that it converts an interface or class into another interface that the client needs.
What are the elements of the Adapter design pattern?
The Adapter pattern parts are
- Target: the interface we use to create the adapter.
- Adapter: is the implementation of the target and will take care of the adaptation.
- Client: it is the one that interacts with and uses the adapter.
- Adaptee: it is the incompatible interface that we need to adapt with the adapter.
How to create the Adapter design pattern with Java?
We mentioned earlier that the pattern had several parts. A client that interacts with the Adapter to get what it needs. The Target from which the Adapter will be implemented, who will be in charge of adapting the Adaptee response so that the Client understands it. The Adaptee that represents the class with an incompatible interface for the client
Let’s take this to an example where a Client service needs information from a BankService service that returns the information in the BankData class.
- Client: the client service needs information from a bank account and needs it in the User class.
- Client: the client calls the adapter to get information from the User.
- Adapter: the adapter calls the service to return information.
- Adaptee: the service returns the BankUser, the Adaptee.
- Adapter: the adapter converts BankData to User and returns it to the client.
- Client: the client receives the info in User. The class User that the client can understand.
Let’s see the classes:
The service that returns the BankData
package patterns.adapter;
public class BankService {
public BankData findByAccountNumber(int accountNumber) {
if (accountNumber == 1) {
return new BankData(accountNumber,"Nick", 100d);
} else if (accountNumber == 3) {
return new BankData(accountNumber,"Susan", 200d);
} else {
return null;
}
}
}
The BankData class with the info
package patterns.adapter;
public class BankData {
private int account;
private String name;
private double balance;
public BankData(int account, String name, double balance) {
this.account = account;
this.name = name;
this.balance = balance;
}
public String getName() {
return name;
}
public int getAccount() {
return account;
}
public double getBalance() {
return balance;
}
}
The User class required by the client
package patterns.adapter;
public class User {
private int id;
private String name;
private double totalAvailable;
public User(int id, String name, double totalAvailable) {
this.id = id;
this.name = name;
this.totalAvailable = totalAvailable;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public double getTotalAvailable() {
return totalAvailable;
}
}
The target (interface) from which we created the Adapter
package patterns.adapter;
public interface UserAdapter {
User getUser(int accountNumber);
}
The implementation of the adapter
package patterns.adapter;
public class UserAdapterImpl implements UserAdapter {
private BankService service = new BankService();
@Override
public User getUser(int accountNumber) {
BankData bankData = service.findByAccountNumber(accountNumber);
User user = new User(bankData.getAccount(), bankData.getName(), bankData.getBalance());
return user;
}
}
The client that needs information in the User class
package patterns.adapter;
public class Client {
public static void main(String[] args) {
UserAdapterImpl adapter = new UserAdapterImpl();
adapter.getUser(1);
}
}
Conclusion
We have seen that the Adapter pattern resolves incompatibilities between interfaces by adapting one interface to the other using an adapter. The adapter is responsible for the conversion between interfaces.
This is a widely used pattern, especially when we communicate with external apis and need to adapt its response to our interfaces.
You can review this code GitHub