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 | |
|---|---|---|
| Mechanism | Composition β wraps the adaptee | Multiple inheritance β inherits from both |
| C# support | Yes (preferred) | Not directly (no multiple inheritance) |
| Flexibility | Works with adaptee subclasses | Tied to one concrete adaptee |
| Overrides | Cannot override adaptee behavior | Can override adaptee methods |
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β adaptStreamto text-based read/writeHttpClientadapters β wrappingHttpClientbehind an interface for testabilityIDbConnectionadapters β ADO.NET providers adapt different databases to a common interface- ASP.NET Core authentication handlers β
IAuthenticationHandleradapts 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.
Related Topicsβ
- 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