The Adapter Pattern: Bridging Incompatible Interfaces

We often encounter situations where we need to use classes or interfaces that don’t quite fit our current system. This is where the Adapter Pattern comes into play. The Adapter Pattern allows us to make incompatible interfaces compatible without modifying their source code.

What is the Adapter Pattern?

The Adapter Pattern is a structural design pattern that enables objects with incompatible interfaces to collaborate. It acts as a bridge between two incompatible interfaces, allowing them to work together seamlessly. The pattern consists of three main components:

  1. Target Interface: The interface expected by the client.
  2. Adaptee Interface: The incompatible interface that needs to be adapted.
  3. Adapter: The class that implements the target interface and adapts the adaptee interface.

The Adapter Pattern offer some benefits:

  1. Increased flexibility: Allows you to use third-party libraries or legacy code without modifying their source.
  2. Improved maintainability: Separates the client code from the adaptee implementation.
  3. Reduced coupling: Decouples the client from specific implementations.

Real-world Example: Adapting Third-party APIs

Let’s consider a real-world scenario where we might need to use the Adapter Pattern. Imagine we’re building an e-commerce application that needs to integrate with multiple payment gateways. Each gateway has its own API, but our system expects a standardized interface for processing payments.

We’ll create an example using C# and the latest .NET 9 ecosystem to demonstrate how the Adapter Pattern can solve this problem.

Step 1: Define the Target Interface

First, let’s define the target interface that our system expects:

public interface IPaymentGateway
{
    void ProcessPayment(decimal amount);
    void RefundPayment(string transactionId);
}

Step 2: Create Concrete Adapters

Now, let’s create concrete adapters for two popular payment gateways: PayPal and Stripe. These adapters will implement our IPaymentGateway interface and adapt the third-party APIs.

PayPalAdapter.cs:

public class PayPalAdapter : IPaymentGateway
{
    private readonly PayPalApi _payPalApi;

    public PayPalAdapter(PayPalApi payPalApi)
    {
        _payPalApi = payPalApi;
    }

    public void ProcessPayment(decimal amount)
    {
        _payPalApi.Pay(amount);
    }

    public void RefundPayment(string transactionId)
    {
        _payPalApi.Refund(transactionId);
    }
}

StripeAdapter.cs:

public class StripeAdapter : IPaymentGateway
{
    private readonly StripeApi _stripeApi;

    public StripeAdapter(StripeApi stripeApi)
    {
        _stripeApi = stripeApi;
    }

    public void ProcessPayment(decimal amount)
    {
        _stripeApi.ChargeCard(amount);
    }

    public void RefundPayment(string transactionId)
    {
        _stripeApi.RefundCharge(transactionId);
    }
}

Step 3: Implement Third-party APIs

Let’s simulate the third-party APIs:

PayPalApi.cs:

public class PayPalApi
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Processing payment of ${amount} via PayPal");
    }

    public void Refund(string transactionId)
    {
        Console.WriteLine($"Refunding transaction {transactionId} via PayPal");
    }
}

StripeApi.cs:

public class StripeApi
{
    public void ChargeCard(decimal amount)
    {
        Console.WriteLine($"Charging card for ${amount} via Stripe");
    }

    public void RefundCharge(string transactionId)
    {
        Console.WriteLine($"Refunding charge {transactionId} via Stripe");
    }
}

Step 4: Client Code

Now, let’s see how our client code can use these adapters seamlessly:

PaymentProcessor.cs:

public class PaymentProcessor
{
    private readonly IPaymentGateway _paymentGateway;

    public PaymentProcessor(IPaymentGateway paymentGateway)
    {
        _paymentGateway = paymentGateway;
    }

    public void ProcessTransaction(decimal amount, string transactionId)
    {
        _paymentGateway.ProcessPayment(amount);
        Console.WriteLine($"Transaction processed successfully. ID: {transactionId}");
    }

    public void InitiateRefund(string transactionId)
    {
        _paymentGateway.RefundPayment(transactionId);
        Console.WriteLine($"Refund initiated for transaction {transactionId}");
    }
}

Usage Example:

class Program
{
    static void Main(string[] args)
    {
        // Using PayPal
        var paypalApi = new PayPalApi();
        var paypalAdapter = new PayPalAdapter(paypalApi);
        var paymentProcessor = new PaymentProcessor(paypalAdapter);

        paymentProcessor.ProcessTransaction(100.00m, "PAYPAL123");
        paymentProcessor.InitiateRefund("PAYPAL123");

        Console.WriteLine();

        // Using Stripe
        var stripeApi = new StripeApi();
        var stripeAdapter = new StripeAdapter(stripeApi);
        paymentProcessor = new PaymentProcessor(stripeAdapter);

        paymentProcessor.ProcessTransaction(50.00m, "STRIPE456");
        paymentProcessor.InitiateRefund("STRIPE456");
    }
}

Output:

Processing payment of $100 via PayPal
Transaction processed successfully. ID: PAYPAL123
Refunding transaction PAYPAL123 via PayPal
Refund initiated for transaction PAYPAL123

Charging card for $50 via Stripe
Transaction processed successfully. ID: STRIPE456
Refunding charge STRIPE456 via Stripe
Refund initiated for transaction STRIPE456

Conclusion

The Adapter Pattern is a powerful tool for integrating incompatible interfaces in modern C# applications. By using adapters, we can seamlessly integrate third-party APIs or legacy systems into our application without modifying their source code. This pattern promotes flexibility, maintainability, and reduces coupling between components.

In this example, we demonstrated how to adapt PayPal and Stripe payment gateways to a standardized interface, allowing our system to work with different payment providers interchangeably. The Adapter Pattern is particularly useful in scenarios involving external services, legacy code integration, or when working with libraries that don’t match your application’s architecture.

Remember, the Adapter Pattern is just one of many design patterns available in software engineering. As you continue to develop your skills, explore other patterns and learn how to apply them appropriately to solve real-world problems efficiently.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *