Adapter Design Pattern: Comprehensive Explanation

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

ScenarioUse Case
Integrating legacy systemsWrap old API with modern interface
Third-party libraries have mismatched interfacese.g., Old payment SDK vs new gateway
Multiple similar interfaces existUnify them into one
No control over source codeAdapt without modification
Prototyping with mocksSimulate 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

DomainExample
Payment ProcessingAdapt Stripe SDK to internal IPaymentService
Data FormatsConvert XML to JSON parsers
UI LibrariesWrap jQuery in React components
Hardware DriversAdapt USB device to OS API
E-CommerceIntegrate 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] Charging1500 with details: 4111111111111111|12/25
Order processed. TX ID: abc12345, Status: APPROVED

UML 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

ParticipantRole in E-Commerce Example
TargetIPaymentService – modern payment interface
AdapteeLegacyPaymentGateway – old incompatible API
AdapterLegacyPaymentAdapter – translates calls
ClientCheckoutService – uses Target seamlessly

Advantages in E-Commerce

BenefitImpact
Legacy integrationReuse old gateways without rewrite
Unified APIOne interface for all payments
MaintainableIsolate legacy logic in adapter
ExtensibleAdd adapters for new legacies

Adapter vs Bridge

AspectAdapterBridge
PurposeCompatibility (retrofit)Decouple abstraction from implementation
WhenExisting incompatible classesDesign-time variation
StructureWraps one adapteeHierarchies on both sides
ExampleLegacy API wrapperUI 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.

Uma Mahesh
Uma Mahesh

Author is working as an Architect in a reputed software company. He is having nearly 21+ Years of experience in web development using Microsoft Technologies.

Articles: 276