Adapter vs Facade Pattern in C#: Key Differences Explained
When you're working with structural design patterns, two of the most commonly compared are the adapter and the facade. They both sit between your application code and something else -- whether that's a third-party library, legacy code, or a set of complex subsystems. But despite looking similar on the surface, these patterns solve very different problems. The adapter vs facade pattern in C# comparison trips up a lot of developers because both patterns involve wrapping existing code behind a new interface. Understanding the distinction between them is critical for writing clean, maintainable architectures.
The adapter pattern converts one interface into another that a client expects. The facade pattern provides a simplified, unified interface to a set of interfaces in a subsystem. One translates. The other simplifies. In this article, we'll break each pattern down individually, compare their implementations side by side using matching C# examples, and give you clear decision criteria for choosing between them. If you want to see how these patterns fit within the full catalog of Gang of Four patterns, check out The Big List of Design Patterns for a comprehensive overview.
Quick Refresher: The Adapter Pattern
Before we get into the adapter vs facade comparison in detail, let's revisit each pattern individually. The adapter pattern lets classes with incompatible interfaces work together. You have an existing class whose interface doesn't match what your client code expects, and you write a thin wrapper -- the adapter -- that translates between the two. The client talks to the adapter through the expected interface, and the adapter translates those calls into the vocabulary of the adaptee.
The defining characteristic of the adapter is that it wraps a single class and converts its interface. It doesn't add functionality. It doesn't simplify anything. It just translates method signatures and data formats so that two pieces of code that were never designed to work together can now cooperate.
Here's a concrete example. Imagine you have a payment processing interface your application expects, but the third-party payment library you're integrating exposes a completely different API:
using System;
public interface IPaymentProcessor
{
bool ProcessPayment(
string customerId,
decimal amount,
string currency);
}
public sealed class StripeGateway
{
public int CreateCharge(
string accountRef,
long amountInCents,
string currencyCode,
string description)
{
Console.WriteLine(
$"Stripe charge: {amountInCents} " +
$"{currencyCode} for {accountRef}");
return 1;
}
}
The IPaymentProcessor is the interface your application uses throughout its codebase. The StripeGateway is the third-party class with a different method name, different parameter types, and a different return type. An adapter bridges the gap:
using System;
public sealed class StripePaymentAdapter : IPaymentProcessor
{
private readonly StripeGateway _stripe;
public StripePaymentAdapter(StripeGateway stripe)
{
_stripe = stripe;
}
public bool ProcessPayment(
string customerId,
decimal amount,
string currency)
{
long amountInCents = (long)(amount * 100);
int chargeId = _stripe.CreateCharge(
customerId,
amountInCents,
currency,
$"Payment for customer {customerId}");
return chargeId > 0;
}
}
The adapter implements IPaymentProcessor, receives the StripeGateway through its constructor, and translates every call. It converts decimal amount to long amountInCents, maps the method name from ProcessPayment to CreateCharge, and converts the int result into a bool. The client code never knows it's talking to Stripe. It just calls ProcessPayment and gets back a boolean. This pattern works beautifully with dependency injection in C#, where you register StripePaymentAdapter as the IPaymentProcessor implementation and the rest of your application stays completely decoupled from the third-party library.
Quick Refresher: The Facade Pattern
Now let's look at the other half of the adapter vs facade equation. The facade pattern provides a unified, simplified interface to a complex subsystem. Instead of requiring the client to interact with multiple classes, manage initialization sequences, and coordinate between components, the facade exposes a single entry point that handles all of that complexity internally.
The defining characteristic of the facade is that it sits in front of multiple classes and orchestrates them. It doesn't translate one interface to another. It reduces the surface area of an entire subsystem to a smaller, easier-to-use API. The facade knows about the subsystem's internals -- its classes, their dependencies, and the order in which things need to happen -- and shields the client from all of that.
Here's the same payment domain, but this time the complexity comes from coordinating multiple subsystem components to process an order:
using System;
public sealed class InventoryService
{
public bool CheckStock(string productId, int quantity)
{
Console.WriteLine(
$"Checking stock for {productId}: " +
$"{quantity} units");
return true;
}
public void ReserveStock(string productId, int quantity)
{
Console.WriteLine(
$"Reserved {quantity} units of {productId}");
}
}
public sealed class PaymentService
{
public bool ChargeCustomer(
string customerId,
decimal amount)
{
Console.WriteLine(
$"Charged {customerId}: ${amount}");
return true;
}
}
public sealed class ShippingService
{
public string CreateShipment(
string customerId,
string productId,
int quantity)
{
string trackingNumber = Guid.NewGuid()
.ToString()[..8];
Console.WriteLine(
$"Shipment created: {trackingNumber}");
return trackingNumber;
}
}
public sealed class NotificationService
{
public void SendOrderConfirmation(
string customerId,
string trackingNumber)
{
Console.WriteLine(
$"Confirmation sent to {customerId}: " +
$"tracking {trackingNumber}");
}
}
Without a facade, the client must call each service in the right order, handle errors at each step, and understand which services depend on which. The facade wraps all of this:
using System;
public sealed class OrderFacade
{
private readonly InventoryService _inventory;
private readonly PaymentService _payment;
private readonly ShippingService _shipping;
private readonly NotificationService _notification;
public OrderFacade(
InventoryService inventory,
PaymentService payment,
ShippingService shipping,
NotificationService notification)
{
_inventory = inventory;
_payment = payment;
_shipping = shipping;
_notification = notification;
}
public bool PlaceOrder(
string customerId,
string productId,
int quantity,
decimal totalPrice)
{
if (!_inventory.CheckStock(productId, quantity))
{
return false;
}
if (!_payment.ChargeCustomer(
customerId,
totalPrice))
{
return false;
}
_inventory.ReserveStock(productId, quantity);
string tracking = _shipping.CreateShipment(
customerId,
productId,
quantity);
_notification.SendOrderConfirmation(
customerId,
tracking);
return true;
}
}
The OrderFacade coordinates four services behind a single PlaceOrder method. The client calls one method and gets back a boolean. It doesn't need to know about inventory checks, payment processing, shipping, or notifications. The facade manages the workflow, the ordering of operations, and the error handling. For a deeper dive into how the facade pattern simplifies complex subsystems, see Facade Design Pattern in C#: How to Simplify Complex Subsystems.
The Key Difference: Intent
Here's the single most important distinction in the adapter vs facade pattern comparison. If you take only one thing away from the adapter vs facade discussion, make it this:
- Adapter converts an interface. It makes an existing class conform to an interface the client already expects.
- Facade simplifies a subsystem. It provides a new, easier interface in front of multiple classes.
The adapter solves an incompatibility problem. You have code that speaks one language and a library that speaks another. The adapter translates between them. The facade solves a complexity problem. You have a subsystem with many moving parts, and the facade gives the client a single, streamlined entry point.
Think of it this way: the adapter vs facade analogy maps cleanly to everyday objects. An adapter is like a power plug converter when you travel internationally. Your laptop has a US plug. The wall socket in Europe expects a different shape. The adapter converts the plug shape so your laptop can connect. Nothing about the electrical system changes -- the adapter just makes two incompatible shapes fit together. A facade is like a hotel concierge. You want dinner reservations, a taxi, and theater tickets. Instead of calling three different services yourself, you tell the concierge what you want and they coordinate everything behind the scenes. The concierge simplifies your interaction with a complex set of services. The adapter vs facade distinction is as clear as converter vs concierge.
Side-by-Side Comparison Table
Here's a quick-reference table showing the core differences between the adapter vs facade pattern in C#. This table captures the structural and behavioral distinctions that matter most when choosing between adapter vs facade for your design:
| Feature | Adapter | Facade |
|---|---|---|
| Primary intent | Convert one interface to another | Simplify access to a complex subsystem |
| Scope | Wraps a single class (the adaptee) | Sits in front of multiple classes |
| Direction | Maps client interface → adaptee interface | Provides new unified interface → subsystem |
| Creates new interface? | No -- implements an existing expected interface | Yes -- defines a new simplified interface |
| Number of wrapped objects | One | Multiple |
| Client awareness | Client uses the expected interface as normal | Client uses the facade's simplified API |
| Typical trigger | Incompatible interfaces that need to work together | Complex subsystem that needs a simpler entry point |
| Structural fingerprint | Class implementing interface A, wrapping class B | Class wrapping services C, D, E, and F |
The adapter wraps one thing and converts it. The facade wraps many things and simplifies them.
Solving the Same Problem: Adapter vs Facade Approach
To really solidify the adapter vs facade pattern in C# distinction, let's look at how each pattern addresses integration concerns in the same e-commerce domain. The adapter vs facade difference becomes clearest when you see both patterns applied to related problems.
The Adapter Approach: Making a Tax Calculator Fit
Your application expects an ITaxCalculator interface, but the tax library you're integrating has its own API:
using System;
public interface ITaxCalculator
{
decimal CalculateTax(
decimal subtotal,
string stateCode);
}
public sealed class ThirdPartyTaxEngine
{
public double ComputeSalesTax(
double price,
string jurisdiction,
int taxYear)
{
Console.WriteLine(
$"Computing tax for {jurisdiction}, " +
$"year {taxYear}");
return price * 0.08;
}
}
public sealed class TaxEngineAdapter : ITaxCalculator
{
private readonly ThirdPartyTaxEngine _engine;
public TaxEngineAdapter(ThirdPartyTaxEngine engine)
{
_engine = engine;
}
public decimal CalculateTax(
decimal subtotal,
string stateCode)
{
double result = _engine.ComputeSalesTax(
(double)subtotal,
stateCode,
DateTimeOffset.UtcNow.Year);
return (decimal)result;
}
}
The adapter converts decimal to double, maps stateCode to jurisdiction, supplies the current year that the third-party API requires but our interface doesn't expose, and converts the result back. One class in, one class out, pure translation.
The Facade Approach: Simplifying Checkout
Instead of converting an interface, the facade simplifies the interaction with multiple checkout-related services:
using System;
public sealed class CheckoutFacade
{
private readonly ITaxCalculator _tax;
private readonly IPaymentProcessor _payment;
private readonly InventoryService _inventory;
private readonly ShippingService _shipping;
public CheckoutFacade(
ITaxCalculator tax,
IPaymentProcessor payment,
InventoryService inventory,
ShippingService shipping)
{
_tax = tax;
_payment = payment;
_inventory = inventory;
_shipping = shipping;
}
public bool Checkout(
string customerId,
string productId,
int quantity,
decimal unitPrice,
string stateCode)
{
decimal subtotal = unitPrice * quantity;
decimal tax = _tax.CalculateTax(
subtotal,
stateCode);
decimal total = subtotal + tax;
if (!_inventory.CheckStock(productId, quantity))
{
return false;
}
if (!_payment.ProcessPayment(
customerId,
total,
"USD"))
{
return false;
}
_inventory.ReserveStock(productId, quantity);
_shipping.CreateShipment(
customerId,
productId,
quantity);
return true;
}
}
Notice that the facade coordinates four services. It calculates the tax, checks inventory, processes payment, and creates a shipment -- all behind one Checkout method. The client doesn't need to know the order of operations or which services are involved.
Combining Adapter and Facade
The adapter vs facade pattern comparison isn't an either-or choice. In many real-world applications, the adapter vs facade relationship is complementary -- these patterns frequently appear together in production code, and combining them is a sign of a well-layered architecture.
The most common combination is using adapters behind a facade. The facade simplifies the subsystem for the client, and individual adapters within that subsystem translate third-party APIs into the interfaces the facade expects:
// The adapter makes the third-party tax engine
// conform to ITaxCalculator
var taxAdapter = new TaxEngineAdapter(
new ThirdPartyTaxEngine());
// The adapter makes Stripe conform to
// IPaymentProcessor
var paymentAdapter = new StripePaymentAdapter(
new StripeGateway());
// The facade uses adapted interfaces alongside
// native services
var checkout = new CheckoutFacade(
taxAdapter,
paymentAdapter,
new InventoryService(),
new ShippingService());
// Client calls one method
checkout.Checkout(
"cust-123",
"prod-456",
quantity: 2,
unitPrice: 49.99m,
stateCode: "WA");
In this setup, the adapters handle interface translation at the boundary, and the facade handles workflow orchestration at a higher level. Each pattern operates at a different layer and solves a different problem. The facade doesn't care whether its dependencies are adapters, native implementations, or decorators. It works against interfaces. This layering approach aligns well with inversion of control principles, where each component depends on abstractions rather than concrete implementations.
Decision Flowchart Criteria
When deciding between the adapter vs facade pattern in C#, ask these questions in order. These criteria will help you determine whether adapter vs facade is the right framing for your problem:
1. Are you integrating a single external class whose interface doesn't match what your code expects? If yes, use the adapter. Your problem is interface incompatibility, and the adapter translates one interface to another.
2. Are you dealing with a complex subsystem that has many classes, and your client code needs a simpler way to interact with them? If yes, use the facade. Your problem is complexity, and the facade provides a unified entry point.
3. Do you need both interface translation and subsystem simplification? Use both. Put adapters at the integration boundary to normalize third-party interfaces, then layer a facade on top to provide a streamlined API to your client code.
4. Is the class you're wrapping already compatible with your expected interface, but you want to add behavior? Neither pattern. You're looking at the decorator pattern instead, which adds behavior without changing the interface.
5. Do you need to choose between multiple interchangeable algorithms at runtime? Neither pattern. That's the strategy pattern, which selects an implementation rather than translating or simplifying.
Common Mistakes
Avoiding these adapter vs facade mistakes will save you from unnecessary refactoring and keep your codebase clean.
Using a Facade When You Need an Adapter
If you're wrapping a single class and your facade has only one dependency, you probably don't need a facade. Facades are for coordinating multiple components. If you're just translating one interface to another, that's an adapter. Creating a facade around a single class adds indirection without simplification.
Using an Adapter as a Dumping Ground
Adapters should translate, not add business logic. If your adapter is validating inputs, handling retries, or logging operations, you've given it too many responsibilities. Translation belongs in the adapter. Cross-cutting concerns belong in decorators. Business logic belongs in your domain layer. Keep the adapter thin.
Creating a God Facade
A facade should simplify access to a bounded subsystem, not become a single point of entry for your entire application. If your facade has fifteen dependencies and thirty methods, it's too broad. Split it into focused facades -- one per use case or feature area. Each facade should represent a cohesive set of operations. The composite pattern might help if you find yourself needing to compose multiple facades into a hierarchy.
Ignoring Testability
Both patterns should improve testability, not hinder it. An adapter makes third-party code testable by putting it behind an interface your tests can mock. A facade makes complex workflows testable by isolating the coordination logic. If your adapter takes concrete dependencies or your facade doesn't accept its services through the constructor, you've lost the testability benefit.
Confusing Adapter with Wrapper
Every adapter is a wrapper, but not every wrapper is an adapter. A wrapper is a general term for any class that wraps another. An adapter specifically converts one interface to a different, pre-existing expected interface. If you're creating a new interface to wrap an existing class, that's closer to a facade or a new abstraction -- not an adapter.
Frequently Asked Questions
What is the difference between adapter and facade pattern?
The adapter vs facade difference comes down to scope and intent. The adapter pattern converts one interface into another that the client already expects. The facade pattern provides a new simplified interface to an entire subsystem. The adapter wraps a single class and translates its API. The facade wraps multiple classes and orchestrates them behind a streamlined entry point. Adapter solves interface incompatibility. Facade solves subsystem complexity.
When should I use the adapter pattern instead of the facade?
Use the adapter instead of the facade when you have a specific interface your client code depends on and a class that does what you need but exposes a different API. The adapter translates between the two. If you're integrating a third-party library, legacy service, or external dependency whose methods, parameter types, or return types don't match your expected interface, the adapter is the right choice. The adapter vs facade decision here hinges on whether you're translating for one class or simplifying access to many.
Can a facade contain adapters?
Yes, and this is a common and recommended combination. The facade sits at a higher level and coordinates multiple services. Some of those services might be third-party libraries wrapped in adapters to normalize their interfaces. The facade doesn't know or care that its dependencies are adapted -- it works against abstractions. This layered approach keeps each pattern focused on a single responsibility.
Does the adapter pattern change the behavior of the adapted class?
No. The adapter's job is purely translational. It converts method names, parameter types, and return values without adding, removing, or modifying behavior. If you need to add behavior like logging, caching, or validation, use the decorator pattern on top of the adapter. The adapter translates. The decorator enhances. Keeping these concerns separate makes your code easier to maintain and test.
How does the adapter vs facade pattern relate to dependency injection?
Both patterns in the adapter vs facade comparison integrate naturally with dependency injection. Register the adapter as the implementation of the client's expected interface, and the DI container resolves it wherever that interface is needed. Register the facade as a service that receives its subsystem dependencies through its constructor. Both patterns depend on abstractions, which is the foundation of DI. Using DI with these patterns ensures your application remains loosely coupled and testable.
Is a facade just an adapter for multiple classes?
Not exactly, and this is a critical point in the adapter vs facade comparison. The key difference is that an adapter implements a pre-existing expected interface -- one that the client already depends on. A facade creates a new interface that didn't exist before. The facade designs a simplified API from scratch based on client needs. The adapter conforms to an API the client has already defined. They operate in different directions: the adapter maps to an existing contract, while the facade defines a new one.
Can the adapter and facade patterns be used with other structural patterns?
Absolutely. The adapter vs facade pairing is just one example of how structural patterns compose together. In practice, you might use an adapter behind a facade, a decorator on top of an adapter, or a facade in front of a composite structure. Each structural pattern solves a specific problem -- translation, simplification, behavior enhancement, or hierarchical organization -- and they complement each other well when layered thoughtfully.
Wrapping Up Adapter vs Facade in C#
The adapter vs facade pattern in C# distinction comes down to a single question: are you translating an interface or simplifying a subsystem? If you have a class that does what you need but speaks the wrong language, the adapter converts its interface to the one your client expects. If you have multiple classes that need to be coordinated behind a cleaner entry point, the facade provides that unified API.
Both patterns sit at the boundary between your application code and external complexity. The adapter operates at the class level, wrapping a single adaptee. The facade operates at the subsystem level, orchestrating multiple components. Understanding this scope difference is what prevents you from misapplying one where the other belongs. When you combine them -- adapters behind a facade -- you get the best of both worlds: normalized interfaces at the boundary and simplified workflows at the surface.

