Skip to main content

Adapter Pattern

Definition​

The Adapter converts the interface of a class into another interface that clients expect. It lets classes work together that couldn't otherwise because of incompatible interfaces. Also known as Wrapper.

Coffee Shop Example​

Our Coffee Shop has its own IPaymentProcessor interface. A new third-party payment provider β€” StripePay β€” has a completely different API. Instead of rewriting our code, we build an adapter that wraps StripePay and makes it look like our interface.

Structure​

Target Interface β€” What the Coffee Shop Expects​

// Our standard payment interface
public record PaymentResult(bool Success, string TransactionId, string Message);
public record RefundResult(bool Success, string RefundId, string Message);

public interface IPaymentProcessor
{
PaymentResult ProcessPayment(decimal amount, string currency);
RefundResult Refund(string transactionId);
}

Adaptee β€” The Third-Party SDK​

// Third-party SDK β€” we can't modify this code
public class ChargeRequest
{
public decimal TotalCents { get; set; } // StripePay uses cents, not dollars
public string CurrencyCode { get; set; } = "";
public string CustomerEmail { get; set; } = "";
}

public class ChargeResponse
{
public bool IsSuccessful { get; set; }
public string ChargeId { get; set; } = "";
public string ErrorMessage { get; set; } = "";
}

public class RefundResponse
{
public bool IsSuccessful { get; set; }
public string RefundId { get; set; } = "";
}

// The third-party SDK class
public class StripePaySdk
{
public ChargeResponse Charge(ChargeRequest request) => new()
{
IsSuccessful = true,
ChargeId = $"ch_{Guid.NewGuid():N}"[..16],
};

public RefundResponse IssueRefund(string chargeId) => new()
{
IsSuccessful = true,
RefundId = $"re_{Guid.NewGuid():N}"[..16],
};
}

Adapter β€” Bridging the Two​

public class StripePayAdapter : IPaymentProcessor
{
private readonly StripePaySdk _stripePay;

public StripePayAdapter(StripePaySdk stripePay)
{
_stripePay = stripePay;
}

public PaymentResult ProcessPayment(decimal amount, string currency)
{
// Adapt: dollars β†’ cents, our format β†’ their format
var request = new ChargeRequest
{
TotalCents = amount * 100, // Convert dollars to cents
CurrencyCode = currency.ToUpper(),
};

var response = _stripePay.Charge(request);

// Adapt: their response β†’ our response
return new PaymentResult(
Success: response.IsSuccessful,
TransactionId: response.ChargeId,
Message: response.ErrorMessage
);
}

public RefundResult Refund(string transactionId)
{
var response = _stripePay.IssueRefund(transactionId);

return new RefundResult(
Success: response.IsSuccessful,
RefundId: response.RefundId,
Message: response.IsSuccessful ? "Refund processed" : "Refund failed"
);
}
}

Existing Implementation β€” No Changes Needed​

// Our existing payment implementation
public class CoffeeShopPayment : IPaymentProcessor
{
public PaymentResult ProcessPayment(decimal amount, string currency) =>
new(true, $"local_{Guid.NewGuid():N}"[..16], "Payment processed");

public RefundResult Refund(string transactionId) =>
new(true, $"ref_{Guid.NewGuid():N}"[..16], "Refund processed");
}

Client β€” The Coffee Shop​

public class CoffeeShop
{
private readonly IPaymentProcessor _payment;

public CoffeeShop(IPaymentProcessor payment)
{
_payment = payment;
}

public void OrderCoffee(string coffeeName, decimal price)
{
Console.WriteLine($"Ordering {coffeeName} β€” ${price:F2}");

var result = _payment.ProcessPayment(price, "USD");
Console.WriteLine(result.Success
? $" Payment OK β€” Transaction: {result.TransactionId}"
: $" Payment FAILED β€” {result.Message}");
}
}

// Usage β€” swap payment provider without changing CoffeeShop
var shop = new CoffeeShop(new StripePayAdapter(new StripePaySdk()));
shop.OrderCoffee("Latte", 4.50m);
shop.OrderCoffee("Espresso", 2.50m);

// Output:
// Ordering Latte β€” $4.50
// Payment OK β€” Transaction: ch_a1b2c3d4e5f6g7h8
// Ordering Espresso β€” $2.50
// Payment OK β€” Transaction: ch_i9j0k1l2m3n4o5p6

Class Adapter vs Object Adapter​

Object Adapter (C# default)Class Adapter
MechanismComposition β€” wraps the adapteeMultiple inheritance β€” inherits from both
C# supportYes (preferred)Not directly (no multiple inheritance)
FlexibilityWorks with adaptee subclassesTied to one concrete adaptee
OverridesCannot override adaptee behaviorCan override adaptee methods
tip

C# doesn't support multiple class inheritance, so object adapter (composition) is the standard approach. You can simulate class adapter with interface + base class, but composition is cleaner.

.NET Real-World Usage​

  • StreamReader / StreamWriter β€” adapt Stream to text-based read/write
  • HttpClient adapters β€” wrapping HttpClient behind an interface for testability
  • IDbConnection adapters β€” ADO.NET providers adapt different databases to a common interface
  • ASP.NET Core authentication handlers β€” IAuthenticationHandler adapts external OAuth providers

When to Use​

  • You need to use an existing class but its interface doesn't match what you need
  • You want to integrate a third-party library without coupling your code to its API
  • You need to wrap legacy code behind a clean interface

When NOT to Use​

  • The interfaces are already compatible β€” no adapter needed
  • You can modify the original class β€” just change it directly
  • You're designing a new system β€” design consistent interfaces from the start

Key Takeaways​

  • Adapter bridges incompatible interfaces through composition
  • Client code only sees the target interface β€” the adaptee is hidden
  • New adapters can be added without changing existing client code (OCP)
  • Adapter is about making things work together β€” not adding new behavior

Interview Questions​

Q: What's the difference between Adapter and Facade? Adapter makes an existing interface work with another β€” same functionality, different shape. Facade simplifies a complex subsystem into a cleaner interface. Adapter is about compatibility, Facade is about simplicity.

Q: Is Adapter a class pattern or object pattern? Both exist. Object adapter uses composition (wraps the adaptee) β€” this is the standard in C#. Class adapter uses inheritance β€” requires multiple inheritance, so it's less common in C#.

Q: How does Adapter relate to Dependency Injection? In ASP.NET, you often inject the target interface (IPaymentProcessor) and register the adapter in the DI container:

services.AddScoped<IPaymentProcessor, StripePayAdapter>();

This lets the entire app use the abstraction while the adapter handles the real integration.

  • Facade β€” simplified interface to a subsystem
  • Decorator β€” adds behavior, Adapter changes interface
  • Bridge β€” separates abstraction from implementation up front
  • SOLID β€” DIP β€” depend on abstractions (IPaymentProcessor), not concrete SDKs