The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together. Think of it like a power adapter you use when traveling abroad — your laptop plug does not change, the foreign outlet does not change, but the adapter bridges the gap between them.
In software, you will frequently encounter situations where you need to integrate a third-party library, legacy system, or external service whose interface does not match what your application expects. Rewriting either side is risky and expensive. The Adapter Pattern solves this elegantly.
Imagine your e-commerce application processes payments through a clean, modern interface. A new business requirement demands integration with a legacy payment gateway that has a completely different API — different method names, different parameter formats, and different response structures.
You cannot modify the legacy system (it is maintained by another team or vendor), and you should not pollute your existing codebase with legacy-specific logic scattered across multiple files.
Create an adapter class that implements your application’s expected interface while internally translating calls to the legacy system’s API. Your application code continues to work with the interface it already knows, completely unaware that a different system is handling the actual payment processing behind the scenes.
The adapter acts as a translator, converting requests from one format to another without either side needing to change.
The Adapter Pattern follows the Open/Closed Principle — your existing code remains open for extension (new payment gateways) but closed for modification (no changes to existing business logic). It also promotes programming to interfaces, keeping your code decoupled from specific implementations.
Here we adapt a legacy payment gateway to work with our modern payment interface.
// Modern payment interface our application expects
public interface PaymentProcessor {
PaymentResult processPayment(String customerId, double amount, String currency);
PaymentResult refund(String transactionId, double amount);
}
// Result object
public class PaymentResult {
private boolean success;
private String transactionId;
private String message;
public PaymentResult(boolean success, String transactionId, String message) {
this.success = success;
this.transactionId = transactionId;
this.message = message;
}
// Getters
public boolean isSuccess() { return success; }
public String getTransactionId() { return transactionId; }
public String getMessage() { return message; }
}
// Legacy payment gateway with a completely different interface
public class LegacyPaymentGateway {
public Map<String, Object> submitTransaction(Map<String, String> params) {
System.out.println("Legacy gateway processing: " + params);
Map<String, Object> response = new HashMap<>();
response.put("status_code", 0); // 0 = success in legacy system
response.put("ref_number", "LEG-" + System.currentTimeMillis());
response.put("status_msg", "Transaction approved");
return response;
}
public Map<String, Object> reverseTransaction(String refNumber, String amountInCents) {
System.out.println("Legacy gateway reversing: " + refNumber);
Map<String, Object> response = new HashMap<>();
response.put("status_code", 0);
response.put("ref_number", "REV-" + System.currentTimeMillis());
response.put("status_msg", "Reversal approved");
return response;
}
}
// Adapter: bridges modern interface to legacy gateway
public class LegacyPaymentAdapter implements PaymentProcessor {
private final LegacyPaymentGateway legacyGateway;
public LegacyPaymentAdapter(LegacyPaymentGateway legacyGateway) {
this.legacyGateway = legacyGateway;
}
@Override
public PaymentResult processPayment(String customerId, double amount, String currency) {
// Translate modern parameters to legacy format
Map<String, String> params = new HashMap<>();
params.put("cust_id", customerId);
params.put("amt", String.valueOf((int)(amount * 100))); // Legacy uses cents
params.put("curr", currency.toUpperCase());
Map<String, Object> response = legacyGateway.submitTransaction(params);
// Translate legacy response to modern format
boolean success = (int) response.get("status_code") == 0;
return new PaymentResult(
success,
(String) response.get("ref_number"),
(String) response.get("status_msg")
);
}
@Override
public PaymentResult refund(String transactionId, double amount) {
String amountInCents = String.valueOf((int)(amount * 100));
Map<String, Object> response = legacyGateway.reverseTransaction(transactionId, amountInCents);
boolean success = (int) response.get("status_code") == 0;
return new PaymentResult(
success,
(String) response.get("ref_number"),
(String) response.get("status_msg")
);
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Wrap legacy gateway in adapter
LegacyPaymentGateway legacyGateway = new LegacyPaymentGateway();
PaymentProcessor processor = new LegacyPaymentAdapter(legacyGateway);
// Application code works with the modern interface
PaymentResult result = processor.processPayment("CUST-123", 49.99, "USD");
System.out.println("Success: " + result.isSuccess());
System.out.println("Transaction ID: " + result.getTransactionId());
}
}
The same payment gateway adapter scenario in Python.
from abc import ABC, abstractmethod
from dataclasses import dataclass
import time
@dataclass
class PaymentResult:
success: bool
transaction_id: str
message: str
# Modern payment interface our application expects
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, customer_id: str, amount: float, currency: str) -> PaymentResult:
pass
@abstractmethod
def refund(self, transaction_id: str, amount: float) -> PaymentResult:
pass
# Legacy payment gateway with a completely different interface
class LegacyPaymentGateway:
def submit_transaction(self, params: dict) -> dict:
print(f"Legacy gateway processing: {params}")
return {
"status_code": 0, # 0 = success in legacy system
"ref_number": f"LEG-{int(time.time() * 1000)}",
"status_msg": "Transaction approved"
}
def reverse_transaction(self, ref_number: str, amount_in_cents: str) -> dict:
print(f"Legacy gateway reversing: {ref_number}")
return {
"status_code": 0,
"ref_number": f"REV-{int(time.time() * 1000)}",
"status_msg": "Reversal approved"
}
# Adapter: bridges modern interface to legacy gateway
class LegacyPaymentAdapter(PaymentProcessor):
def __init__(self, legacy_gateway: LegacyPaymentGateway):
self._legacy_gateway = legacy_gateway
def process_payment(self, customer_id: str, amount: float, currency: str) -> PaymentResult:
# Translate modern parameters to legacy format
params = {
"cust_id": customer_id,
"amt": str(int(amount * 100)), # Legacy uses cents
"curr": currency.upper()
}
response = self._legacy_gateway.submit_transaction(params)
return PaymentResult(
success=response["status_code"] == 0,
transaction_id=response["ref_number"],
message=response["status_msg"]
)
def refund(self, transaction_id: str, amount: float) -> PaymentResult:
amount_in_cents = str(int(amount * 100))
response = self._legacy_gateway.reverse_transaction(transaction_id, amount_in_cents)
return PaymentResult(
success=response["status_code"] == 0,
transaction_id=response["ref_number"],
message=response["status_msg"]
)
# Usage
if __name__ == "__main__":
legacy_gateway = LegacyPaymentGateway()
processor = LegacyPaymentAdapter(legacy_gateway)
result = processor.process_payment("CUST-123", 49.99, "USD")
print(f"Success: {result.success}")
print(f"Transaction ID: {result.transaction_id}")
HandlerAdapter adapts different handler types to the DispatcherServletInputStreamReader adapts a byte stream (InputStream) to a character stream (Reader)csv.DictReader — Adapts CSV file reading into dictionary-based access