The Adapter pattern is a structural design pattern that enables incompatible interfaces to work together by wrapping an existing class with a new interface. It acts as a bridge between two disparate systems, allowing seamless integration without modifying the original code.
Core Intent
“Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”
This facilitates compatibility and reusability of legacy or third-party components.
When to Use Adapter
| Scenario | Use Case |
|---|---|
| Integrating legacy systems | Wrap old API with modern interface |
| Third-party libraries have mismatched interfaces | e.g., Old payment SDK vs new gateway |
| Multiple similar interfaces exist | Unify them into one |
| No control over source code | Adapt without modification |
| Prototyping with mocks | Simulate interfaces for testing |
Avoid when:
- Over-engineering simple compatibility → Direct mapping suffices
- Performance is critical → Adds indirection overhead
Where It Is Used in Real Systems
| Domain | Example |
|---|---|
| Payment Processing | Adapt Stripe SDK to internal IPaymentService |
| Data Formats | Convert XML to JSON parsers |
| UI Libraries | Wrap jQuery in React components |
| Hardware Drivers | Adapt USB device to OS API |
| E-Commerce | Integrate old shipping API with modern order system |
Key Benefits
- Reusability: Leverage existing code without changes
- Separation of concerns: Isolate adaptation logic
- Open/Closed Principle: Extend without modifying adaptees
- Testability: Mock adapters for unit tests
- Flexibility: Support multiple adaptees
Real-World Example: E-Commerce Legacy Payment Integration
An e-commerce platform uses a modern internal payment service (IPaymentService) for new gateways. However, it must integrate a legacy third-party payment system (LegacyPaymentGateway) with outdated methods (e.g., ChargeCard instead of ProcessPayment).
The adapter translates legacy calls to the internal interface, enabling seamless use in checkout flows.
C# Implementation
using System;
// ==================== TARGET INTERFACE ====================
public interface IPaymentService
{
bool ProcessPayment(decimal amount, string cardNumber, string expiry);
string GetTransactionId();
string GetStatus();
}
// ==================== ADAPTEE (LEGACY) ====================
public class LegacyPaymentGateway
{
// Legacy method - incompatible signature
public bool ChargeCard(string cardDetails, decimal chargeAmount)
{
// Simulate legacy processing (e.g., old API call)
Console.WriteLine($"[Legacy] Charging ₹{chargeAmount} with details: {cardDetails}");
return true; // Simulate success
}
public string GetLastTransactionRef()
{
return Guid.NewGuid().ToString("N")[..8]; // Simulated ref
}
public string GetChargeStatus(string refId)
{
return refId.StartsWith("ABCD") ? "APPROVED" : "PENDING";
}
}
// ==================== ADAPTER ====================
public class LegacyPaymentAdapter : IPaymentService
{
private readonly LegacyPaymentGateway _legacyGateway;
private string _transactionRef;
public LegacyPaymentAdapter(LegacyPaymentGateway legacyGateway)
{
_legacyGateway = legacyGateway ?? throw new ArgumentNullException(nameof(legacyGateway));
}
// Translate Target method to Adaptee
public bool ProcessPayment(decimal amount, string cardNumber, string expiry)
{
// Adapt parameters: combine cardNumber + expiry into legacy format
string cardDetails = $"{cardNumber}|{expiry}";
bool success = _legacyGateway.ChargeCard(cardDetails, amount);
if (success)
{
_transactionRef = _legacyGateway.GetLastTransactionRef();
}
return success;
}
public string GetTransactionId() => _transactionRef ?? "N/A";
public string GetStatus()
{
if (string.IsNullOrEmpty(_transactionRef)) return "NOT_STARTED";
return _legacyGateway.GetChargeStatus(_transactionRef);
}
}
// ==================== CLIENT ====================
public class CheckoutService
{
private readonly IPaymentService _paymentService;
public CheckoutService(IPaymentService paymentService)
{
_paymentService = paymentService ?? throw new ArgumentNullException(nameof(paymentService));
}
public string ProcessOrder(decimal total, string cardNumber, string expiry)
{
bool paid = _paymentService.ProcessPayment(total, cardNumber, expiry);
if (paid)
{
string txId = _paymentService.GetTransactionId();
string status = _paymentService.GetStatus();
return $"Order processed. TX ID: {txId}, Status: {status}";
}
return "Payment failed.";
}
}
// ==================== DEMO ====================
class Program
{
static void Main()
{
// Legacy system
var legacyGateway = new LegacyPaymentGateway();
// Adapter wraps legacy
var adapter = new LegacyPaymentAdapter(legacyGateway);
// Client uses Target interface only
var checkout = new CheckoutService(adapter);
Console.WriteLine("=== ADAPTER PATTERN IN E-COMMERCE ===\n");
string result = checkout.ProcessOrder(1500.00m, "4111111111111111", "12/25");
Console.WriteLine(result);
}
}Sample Output
=== ADAPTER PATTERN IN E-COMMERCE ===
[Legacy] Charging ₹1500 with details: 4111111111111111|12/25
Order processed. TX ID: abc12345, Status: APPROVEDUML Class Diagram

Participants in the Adapter Pattern
The classes and objects participating in this pattern include:
- Target (IPaymentService)
- Defines the desired interface that clients use.
- Client
- Interacts with the Target interface, unaware of the adapter.
- Adaptee (LegacyPaymentGateway)
- The existing class with an incompatible interface.
- Adapter (LegacyPaymentAdapter)
- Implements the Target interface.
- Wraps the `Adaptee** and translates calls.
Summary
| Participant | Role in E-Commerce Example |
|---|---|
| Target | IPaymentService – modern payment interface |
| Adaptee | LegacyPaymentGateway – old incompatible API |
| Adapter | LegacyPaymentAdapter – translates calls |
| Client | CheckoutService – uses Target seamlessly |
Advantages in E-Commerce
| Benefit | Impact |
|---|---|
| Legacy integration | Reuse old gateways without rewrite |
| Unified API | One interface for all payments |
| Maintainable | Isolate legacy logic in adapter |
| Extensible | Add adapters for new legacies |
Adapter vs Bridge
| Aspect | Adapter | Bridge |
|---|---|---|
| Purpose | Compatibility (retrofit) | Decouple abstraction from implementation |
| When | Existing incompatible classes | Design-time variation |
| Structure | Wraps one adaptee | Hierarchies on both sides |
| Example | Legacy API wrapper | UI theme + renderer |
Conclusion
The Adapter pattern is essential for e-commerce when integrating legacy or third-party systems without disrupting modern code. It ensures compatibility and evolvability, allowing platforms to incorporate diverse payment gateways efficiently.




