Bridging Diverse Systems with the Adapter Pattern
The Adapter pattern serves as a bridge between incompatible interfaces, enabling seamless integration of existing componants into new systems without altering their original structure. This is particular useful in scenarios where third-party services—such as Alipay or WeChat Pay—must be incorporated into an e-commerce platform that already has its own payment interface.
Core Concept
The Adapter pattern introduces a wrapper class (the adapter) that translates one interface into another expected by the client. This allows disparate systems to work together harmoniously. There are two primary forms:
- Object Adapter: Uses composition—where the adapter holds a reference to the adaptee.
- Class Adapter: Uses inheritance—where the adapter extends the adaptee and implements the target interface.
Structural Components
- Target Interface: The interface the client expects to use.
- Adaptee Class: The existing class with methods that need to be accessed.
- Adapter Class: The bridge that transforms calls from the Target enterface into calls on the Adaptee.
Implementation Examples
Object Adapter
interface PaymentGateway {
void processPayment();
}
class AlipayService {
public void makePayment() {
System.out.println("Processing payment via Alipay");
}
}
class AlipayAdapter implements PaymentGateway {
private final AlipayService alipay;
public AlipayAdapter(AlipayService alipay) {
this.alipay = alipay;
}
@Override
public void processPayment() {
alipay.makePayment();
}
}
Class Adapter
interface PaymentGateway {
void processPayment();
}
class AlipayService {
public void makePayment() {
System.out.println("Processing payment via Alipay");
}
}
class AlipayAdapter extends AlipayService implements PaymentGateway {
@Override
public void processPayment() {
makePayment();
}
}
Client Usage
public class PaymentClient {
public static void main(String[] args) {
AlipayService alipay = new AlipayService();
PaymentGateway gateway = new AlipayAdapter(alipay);
gateway.processPayment(); // Output: Processing payment via Alipay
}
}
Advantages and Limitations
Pros:
- Decouples clients from specific implementations.
- Promotes code reuse by wrapping legacy classes.
- Separates adaptation logic from business logic.
Cons:
- Class adapter limited to single inheritance in Java.
- Adds complexity through additional classes and interfaces.
Applicable Scenarios
- Integrating external libraries with non-matching APIs.
- Reusing existing classes within a system that requires different method signatures.
Advanced Variants
Bidirectional Adapter
Enables mutual communication between two incompatible interfaces by holding references to both.
interface ServiceEndpoint {
void sendToClient(String message);
}
interface ClientEndpoint {
void sendToService(String message);
}
class ServiceImpl implements ServiceEndpoint {
@Override
public void sendToClient(String msg) {
System.out.println("Service received: " + msg);
}
}
class ClientImpl implements ClientEndpoint {
@Override
public void sendToService(String msg) {
System.out.println("Client received: " + msg);
}
}
class BiDirectionalAdapter implements ServiceEndpoint, ClientEndpoint {
private final ServiceImpl service;
private final ClientImpl client;
public BiDirectionalAdapter(ServiceImpl service, ClientImpl client) {
this.service = service;
this.client = client;
}
@Override
public void sendToClient(String msg) {
client.sendToService(msg);
}
@Override
public void sendToService(String msg) {
service.sendToClient(msg);
}
}
Default Adapter Pattern
Reduces boilerplate when implementing large interfaces. It provides default empty implementations, so subclasses only override needed methods.
interface ViewRenderer {
void renderBanner();
void renderColor();
void renderButton();
void renderCommand();
void renderStyles();
void renderShape();
}
abstract class BaseViewAdapter implements ViewRenderer {
public void renderBanner() {}
public void renderColor() {}
public void renderButton() {}
public void renderCommand() {}
public void renderStyles() {}
public void renderShape() {}
}
class CustomLinuxView extends BaseViewAdapter {
@Override
public void renderCommand() {
System.out.println("Displaying command prompt");
}
}
This pattern simplifies development when full interface implementation is unnecessary.