Design Patterns, Explained Simply · Structural Patterns: Fitting Objects Together
Facade
Give a complicated subsystem one simple front door, so most callers can do the common task with one call instead of learning ten classes.
The problem
Placing an order touches five subsystems: inventory (reserve stock), payments (charge), shipping (create a label), email (send a receipt), and analytics (track the sale). Every place that places an order (the web checkout, the mobile API, the admin "reorder" button) repeats the same twelve lines in the same order.
When the payment step changes, three copies must be updated. One of them is forgotten, and admin reorders skip the stock reservation.
Real-world analogy
A hotel concierge. You say "I'd like dinner and theatre tickets for tonight". The concierge calls the restaurant, the box office and a taxi company, in the right order. You don't need to know their phone numbers or booking rules; you talk to one person.
Without the pattern
# repeated in web checkout, mobile API and admin reorder
inventory.reserve(sku, qty)
payment_id = payments.charge(customer, total)
label = shipping.create_label(address, sku)
mailer.send_receipt(customer, payment_id)
analytics.track("order_placed", total)
The pattern
- Facade (
OrderFacade): a class with a few simple, task-focused methods (place_order()) that coordinate the subsystem objects in the right order. - Subsystem classes: keep working exactly as before. The facade doesn't hide them completely; advanced callers can still use them directly.
- Clients: call the facade for the common case.
A facade simplifies; it doesn't change interfaces (that is Adapter) or add behaviour (that is Decorator).
The code
class Inventory:
def reserve(self, sku, qty):
print(f" inventory: reserved {qty} x {sku}")
class Payments:
def charge(self, customer, cents):
print(f" payments: charged {customer} {cents} cents")
return "pay_123"
class Shipping:
def create_label(self, address):
print(f" shipping: label for {address}")
return "TRACK-9"
class Mailer:
def send_receipt(self, customer, payment_id, tracking):
print(f" email: receipt to {customer} ({payment_id}, {tracking})")
class OrderFacade: # one simple front door
def __init__(self, inventory, payments, shipping, mailer):
self.inventory, self.payments = inventory, payments
self.shipping, self.mailer = shipping, mailer
def place_order(self, customer, address, sku, qty, cents):
self.inventory.reserve(sku, qty) # the order of steps lives in one place
payment_id = self.payments.charge(customer, cents)
tracking = self.shipping.create_label(address)
self.mailer.send_receipt(customer, payment_id, tracking)
return tracking
orders = OrderFacade(Inventory(), Payments(), Shipping(), Mailer())
print("web checkout:")
print("tracking number:", orders.place_order("[email protected]", "12 Main St", "MUG-01", 2, 2400))
Output
web checkout: inventory: reserved 2 x MUG-01 payments: charged [email protected] 2400 cents shipping: label for 12 Main St email: receipt to [email protected] (pay_123, TRACK-9) tracking number: TRACK-9
class Inventory {
reserve(sku: string, qty: number) { console.log(` inventory: reserved ${qty} x ${sku}`); }
}
class Payments {
charge(customer: string, cents: number) {
console.log(` payments: charged ${customer} ${cents} cents`);
return "pay_123";
}
}
class Shipping {
createLabel(address: string) {
console.log(` shipping: label for ${address}`);
return "TRACK-9";
}
}
class Mailer {
sendReceipt(customer: string, paymentId: string, tracking: string) {
console.log(` email: receipt to ${customer} (${paymentId}, ${tracking})`);
}
}
class OrderFacade { // one simple front door
constructor(private inventory: Inventory, private payments: Payments,
private shipping: Shipping, private mailer: Mailer) {}
placeOrder(customer: string, address: string, sku: string, qty: number, cents: number): string {
this.inventory.reserve(sku, qty); // the order of steps lives in one place
const paymentId = this.payments.charge(customer, cents);
const tracking = this.shipping.createLabel(address);
this.mailer.sendReceipt(customer, paymentId, tracking);
return tracking;
}
}
const orders = new OrderFacade(new Inventory(), new Payments(), new Shipping(), new Mailer());
console.log("web checkout:");
console.log("tracking number:", orders.placeOrder("[email protected]", "12 Main St", "MUG-01", 2, 2400));
Output
web checkout: inventory: reserved 2 x MUG-01 payments: charged [email protected] 2400 cents shipping: label for 12 Main St email: receipt to [email protected] (pay_123, TRACK-9) tracking number: TRACK-9
public class Main {
public static void main(String[] args) {
var orders = new OrderFacade(new Inventory(), new Payments(), new Shipping(), new Mailer());
System.out.println("web checkout:");
System.out.println("tracking number: " + orders.placeOrder("[email protected]", "12 Main St", "MUG-01", 2, 2400));
}
}
class Inventory {
void reserve(String sku, int qty) { System.out.println(" inventory: reserved " + qty + " x " + sku); }
}
class Payments {
String charge(String customer, int cents) {
System.out.println(" payments: charged " + customer + " " + cents + " cents");
return "pay_123";
}
}
class Shipping {
String createLabel(String address) {
System.out.println(" shipping: label for " + address);
return "TRACK-9";
}
}
class Mailer {
void sendReceipt(String customer, String paymentId, String tracking) {
System.out.println(" email: receipt to " + customer + " (" + paymentId + ", " + tracking + ")");
}
}
class OrderFacade { // one simple front door
private final Inventory inventory;
private final Payments payments;
private final Shipping shipping;
private final Mailer mailer;
OrderFacade(Inventory inventory, Payments payments, Shipping shipping, Mailer mailer) {
this.inventory = inventory; this.payments = payments; this.shipping = shipping; this.mailer = mailer;
}
String placeOrder(String customer, String address, String sku, int qty, int cents) {
inventory.reserve(sku, qty); // the order of steps lives in one place
String paymentId = payments.charge(customer, cents);
String tracking = shipping.createLabel(address);
mailer.sendReceipt(customer, paymentId, tracking);
return tracking;
}
}
Output
web checkout: inventory: reserved 2 x MUG-01 payments: charged [email protected] 2400 cents shipping: label for 12 Main St email: receipt to [email protected] (pay_123, TRACK-9) tracking number: TRACK-9
Run this on your computer — see “Get Started” for setup.
<?php
class Inventory {
public function reserve(string $sku, int $qty): void { echo " inventory: reserved $qty x $sku\n"; }
}
class Payments {
public function charge(string $customer, int $cents): string {
echo " payments: charged $customer $cents cents\n";
return "pay_123";
}
}
class Shipping {
public function createLabel(string $address): string {
echo " shipping: label for $address\n";
return "TRACK-9";
}
}
class Mailer {
public function sendReceipt(string $customer, string $paymentId, string $tracking): void {
echo " email: receipt to $customer ($paymentId, $tracking)\n";
}
}
class OrderFacade { // one simple front door
public function __construct(private Inventory $inventory, private Payments $payments,
private Shipping $shipping, private Mailer $mailer) {}
public function placeOrder(string $customer, string $address, string $sku, int $qty, int $cents): string {
$this->inventory->reserve($sku, $qty); // the order of steps lives in one place
$paymentId = $this->payments->charge($customer, $cents);
$tracking = $this->shipping->createLabel($address);
$this->mailer->sendReceipt($customer, $paymentId, $tracking);
return $tracking;
}
}
$orders = new OrderFacade(new Inventory(), new Payments(), new Shipping(), new Mailer());
echo "web checkout:\n";
$tracking = $orders->placeOrder("[email protected]", "12 Main St", "MUG-01", 2, 2400);
echo "tracking number: $tracking\n";
Output
web checkout: inventory: reserved 2 x MUG-01 payments: charged [email protected] 2400 cents shipping: label for 12 Main St email: receipt to [email protected] (pay_123, TRACK-9) tracking number: TRACK-9
The web checkout, mobile API and admin reorder now all call place_order(). The sequence lives in one place, so the forgotten-step bug can't happen again.
When to use it
- A subsystem has many classes and a correct calling order, and most callers only need a few common tasks.
- You want to decouple application code from a complex library so it can be replaced later.
- You are layering a system: each layer exposes a facade to the layer above.
When not to use it
- The facade turns into a god object that does everything. Keep each facade focused on one area (orders, reports), and split it if it grows.
- Callers need fine control. Let them use the subsystem directly; a facade is a convenience, not a wall.
- There is only one simple class behind it. The facade adds a layer for nothing.
You've already used it
- Laravel "facades" (
Cache::get(),Mail::send()) give a short static-looking front door to services in the container. The name comes from this pattern, though they are technically static proxies to container services. - jQuery:
$.ajax()is a facade overXMLHttpRequestand browser differences. - SDK clients:
boto3.client("s3").upload_file()hides multipart uploads, retries and threading behind one call.
Key points
- Facade gives a complex subsystem one simple, task-focused interface.
- It coordinates subsystem calls in the right order, in one place.
- The subsystem stays available for advanced use.
- Keep facades focused so they don't become god objects.
- Further reading: Refactoring Guru: Facade.
Practice questions
What is the intent of the Facade pattern?
Answer
To provide one simple interface to a complex subsystem for the common tasks, so clients don't need to know its many classes or the correct order of calls. The subsystem stays available for advanced use.
How does Facade differ from Adapter?
Answer
Adapter converts one existing interface into another that a client expects (usually one class). Facade defines a new, simpler interface over a whole subsystem of many classes. Adapter is about compatibility; Facade is about simplicity.
Are Laravel facades an implementation of the GoF Facade pattern?
Answer
Partly. They give a simple front door (Cache::get()) to services, which matches Facade's intent, but technically they are static proxies that resolve the real service from the container on each call. That is why they can be swapped with fakes in tests (Cache::fake()-style helpers).
Open in the app to mark lessons complete, take notes and track your progress.