Facade Pattern
Definitionβ
The Facade provides a unified, simplified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use by hiding its complexity behind a clean front.
Coffee Shop Exampleβ
Placing a coffee order involves many subsystems: checking inventory, brewing coffee, applying loyalty points, processing payment, and printing the receipt. The customer shouldn't need to know about all these steps. The CoffeeShopFacade handles the orchestration.
Structureβ
Subsystem Componentsβ
// --- Subsystem: Inventory ---
public class InventoryService
{
private readonly Dictionary<string, int> _stock = new()
{
["Espresso"] = 50,
["Latte"] = 30,
["Cappuccino"] = 25,
["Cold Brew"] = 15,
};
public bool CheckAvailability(string coffee) =>
_stock.GetValueOrDefault(coffee, 0) > 0;
public void Deduct(string coffee) =>
_stock[coffee] = _stock[coffee] - 1;
}
// --- Subsystem: Brewing ---
public record Beverage(string Name, CupSize Size, DateTime BrewedAt);
public class BrewingService
{
public Beverage Brew(string coffee, CupSize size)
{
Console.WriteLine($" Brewing {size} {coffee}...");
return new Beverage(coffee, size, DateTime.UtcNow);
}
}
// --- Subsystem: Loyalty ---
public class LoyaltyService
{
private readonly Dictionary<string, int> _points = new();
public int AddPoints(string customer, decimal amount)
{
var earned = (int)(amount * 10);
_points[customer] = _points.GetValueOrDefault(customer) + earned;
return _points[customer];
}
public int GetPoints(string customer) =>
_points.GetValueOrDefault(customer);
}
// --- Subsystem: Payment ---
public record PaymentResult(bool Success, string TransactionId);
public class PaymentService
{
public PaymentResult Charge(string customer, decimal amount)
{
Console.WriteLine($" Charging ${amount:F2} for {customer}...");
return new PaymentResult(true, $"txn_{Guid.NewGuid():N}"[..12]);
}
}
// --- Subsystem: Receipt ---
public record Order(string Customer, string Coffee, CupSize Size,
decimal Total, string TransactionId, int PointsEarned);
public class ReceiptPrinter
{
public string Print(Order order) =>
$" Receipt: {order.Size} {order.Coffee} | " +
$"${order.Total:F2} | Txn: {order.TransactionId} | " +
$"+{order.PointsEarned} pts";
}
Facade β Simplified Interfaceβ
public record OrderResult(bool Success, Order Order, string Message);
public class CoffeeShopFacade
{
private readonly InventoryService _inventory;
private readonly BrewingService _brewing;
private readonly LoyaltyService _loyalty;
private readonly PaymentService _payment;
private readonly ReceiptPrinter _receipt;
private static readonly Dictionary<string, decimal> _prices = new()
{
["Espresso"] = 2.50m,
["Latte"] = 4.00m,
["Cappuccino"] = 3.75m,
["Cold Brew"] = 3.50m,
};
private static readonly Dictionary<CupSize, decimal> _sizeMarkup = new()
{
[CupSize.Small] = 0.0m,
[CupSize.Medium] = 0.50m,
[CupSize.Large] = 1.00m,
};
public CoffeeShopFacade(
InventoryService inventory,
BrewingService brewing,
LoyaltyService loyalty,
PaymentService payment,
ReceiptPrinter receipt)
{
_inventory = inventory;
_brewing = brewing;
_loyalty = loyalty;
_payment = payment;
_receipt = receipt;
}
public OrderResult PlaceOrder(string customer, string coffee, CupSize size)
{
// Step 1: Check inventory
if (!_inventory.CheckAvailability(coffee))
return new OrderResult(false, null!, $"Sorry, {coffee} is out of stock.");
// Step 2: Calculate price
var basePrice = _prices[coffee];
var total = basePrice + _sizeMarkup[size];
// Step 3: Process payment
var payment = _payment.Charge(customer, total);
if (!payment.Success)
return new OrderResult(false, null!, "Payment failed.");
// Step 4: Brew the coffee
var beverage = _brewing.Brew(coffee, size);
// Step 5: Add loyalty points
var points = _loyalty.AddPoints(customer, total);
// Step 6: Deduct inventory
_inventory.Deduct(coffee);
// Step 7: Print receipt
var order = new Order(customer, coffee, size, total, payment.TransactionId, points);
Console.WriteLine(_receipt.Print(order));
return new OrderResult(true, order, $"Here's your {size} {coffee}!");
}
}
Client β Dead Simpleβ
// The client doesn't know about inventory, brewing, loyalty, or payment subsystems
var facade = new CoffeeShopFacade(
new InventoryService(),
new BrewingService(),
new LoyaltyService(),
new PaymentService(),
new ReceiptPrinter());
var result1 = facade.PlaceOrder("Alice", "Latte", CupSize.Large);
Console.WriteLine($" β {result1.Message}\n");
var result2 = facade.PlaceOrder("Bob", "Espresso", CupSize.Small);
Console.WriteLine($" β {result2.Message}\n");
// Output:
// Charging $5.00 for Alice...
// Brewing Large Latte...
// Receipt: Large Latte | $5.00 | Txn: txn_a1b2c3d4e5f6 | +50 pts
// β Here's your Large Latte!
//
// Charging $2.50 for Bob...
// Brewing Small Espresso...
// Receipt: Small Espresso | $2.50 | Txn: txn_g7h8i9j0k1l2 | +25 pts
// β Here's your Small Espresso!
The client calls one method (PlaceOrder) instead of coordinating 5 subsystems. The facade doesn't add business logic β it orchestrates existing subsystems.
Facade vs Mediatorβ
| Facade | Mediator | |
|---|---|---|
| Intent | Simplify a subsystem's interface | Centralize communication between colleagues |
| Direction | One-way: client β facade β subsystems | Multi-way: colleagues β mediator β colleagues |
| Awareness | Subsystems don't know about the facade | Colleagues know about the mediator |
| Coupling | Reduces client-subsystem coupling | Reduces colleague-colleague coupling |
.NET Real-World Usageβ
IHostBuilderin .NET β simplifies host configuration behindCreateBuilder()WebApplication.CreateBuilder()β facade over logging, configuration, DI, KestrelHttpClientβ facade over HTTP message handlers, connection pools, and serialization- ORM contexts (
DbContext) β facade over connection, transaction, change tracking, and querying
When to Useβ
- You need a simple interface to a complex subsystem
- You want to layer your system β a facade for each layer
- You need to decouple client code from subsystem implementation details
When NOT to Useβ
- The subsystem is already simple β a facade adds unnecessary indirection
- You need fine-grained control over individual subsystems β the facade hides too much
- The facade becomes a "god object" β split into multiple facades if it grows too large
Key Takeawaysβ
- Facade provides a simplified, unified interface to a complex subsystem
- It orchestrates subsystems β it doesn't contain business logic itself
- Subsystems remain fully functional without the facade β it's optional, not mandatory
- Multiple facades can wrap the same subsystem for different client needs
Interview Questionsβ
Q: Is Facade just a wrapper? Not exactly. A wrapper (like Adapter) converts one interface to another. A Facade simplifies many interfaces into one. It doesn't translate β it abstracts away complexity.
Q: Can a facade access subsystems directly or only through interfaces? Both work. For testability, the facade should depend on abstractions (interfaces). In practice, it may directly use subsystem classes if they're stable.
Q: What's the difference between Facade and Application Service in Clean Architecture? They're conceptually similar. An Application Service (use case) in Clean Architecture often acts as a facade β orchestrating domain objects and infrastructure. The pattern is the same; the naming comes from the architectural layer.
Related Topicsβ
- Adapter β converts interface, Facade simplifies it
- Mediator β centralizes communication between objects
- Clean Architecture β Application Services act as facades
- Dependency Injection β facades receive subsystems via DI