Booking a movie ticket means reserving a seat, charging a card and sending a ticket, in that order, with failure handling in between. If every client (mobile app, website, kiosk) orchestrates those calls itself, they all depend on every subsystem and duplicate the same logic. Facade puts one simple class in front of the subsystem, with methods for the common workflows.
Intent#
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. (GoF)
Analogy: a car's start button. Behind it, the fuel pump primes, the starter engages and the ignition fires. You just press one button.
The problem: clients orchestrate the subsystem#
Without a facade, each client calls the subsystem classes itself, in the right order:
class SeatManager:
def reserve_seat(self, seat_id: str) -> bool:
print(f"Seat {seat_id} reserved.")
return True
class PaymentProcessor:
def charge_card(self, amount: float) -> bool:
print(f"Charged ${amount:.2f}.")
return True
class NotificationService:
def send_ticket(self, email: str) -> None:
print(f"Ticket sent to {email}.")
class MobileAppClient:
def on_checkout_clicked(self, seat_id: str, amount: float,
email: str) -> None:
# The client must know every class AND the exact order to call them
seats = SeatManager()
payment = PaymentProcessor()
notifier = NotificationService()
if seats.reserve_seat(seat_id):
if payment.charge_card(amount):
notifier.send_ticket(email)
- High coupling: the app depends on three backend classes. Adding a
FraudDetectionServicemeans rewriting client code. - Duplication: a website client and a kiosk client copy the same orchestration.
Structure#
A facade sits between the client and the subsystem, so the client makes one call instead of many.
| Participant | Role |
|---|---|
| Facade | Knows which subsystem classes handle what; offers simple methods for common tasks |
| Subsystem classes | Do the real work; they don't know the facade exists |
| Client | Calls the facade instead of the subsystems |
A facade doesn't prevent direct access to the subsystem. It offers a simpler path for the common cases; advanced clients can still go underneath.
Example 1: ticket booking#
The facade wraps the three booking services behind one method:
class TicketBookingFacade:
def __init__(self, seats: SeatManager, payment: PaymentProcessor,
notifier: NotificationService):
self.seats = seats
self.payment = payment
self.notifier = notifier
def book_movie_ticket(self, seat_id: str, amount: float,
email: str) -> bool:
"""The one entry point every client uses."""
if not self.seats.reserve_seat(seat_id):
print("Seat unavailable.")
return False
if not self.payment.charge_card(amount):
print("Payment failed.")
# A real system would release the seat here
return False
self.notifier.send_ticket(email)
return True
class MobileAppClient:
def __init__(self, booking: TicketBookingFacade):
self.booking = booking
def on_checkout_clicked(self) -> None:
self.booking.book_movie_ticket("A1", 15.00, "user@example.com")
facade = TicketBookingFacade(
SeatManager(), PaymentProcessor(), NotificationService()
)
MobileAppClient(facade).on_checkout_clicked()
The client knows nothing about seats, payments or notifications. A new fraud check goes inside the facade, and every client gets it.
Example 2: home theater#
Four devices with their own APIs sit behind a facade with two methods:
class DVDPlayer:
def on(self) -> None: print("DVD: ON")
def stop(self) -> None: print("DVD: stopped")
def off(self) -> None: print("DVD: OFF")
def play(self, movie: str) -> None:
print(f"DVD: playing '{movie}'")
class Projector:
def on(self) -> None: print("Projector: ON")
def off(self) -> None: print("Projector: OFF")
def wide_screen_mode(self) -> None: print("Projector: widescreen")
class SoundSystem:
def on(self) -> None: print("Sound: ON")
def off(self) -> None: print("Sound: OFF")
def set_surround_sound(self) -> None: print("Sound: surround")
def set_volume(self, level: int) -> None:
print(f"Sound: volume {level}")
class Lights:
def on(self) -> None: print("Lights: ON")
def dim(self, level: int) -> None:
print(f"Lights: dimmed to {level}%")
class HomeTheaterFacade:
def __init__(self, dvd: DVDPlayer, projector: Projector,
sound: SoundSystem, lights: Lights):
self._dvd = dvd
self._projector = projector
self._sound = sound
self._lights = lights
# Eight calls in the right order become one
def watch_movie(self, movie: str) -> None:
self._lights.dim(10)
self._projector.on()
self._projector.wide_screen_mode()
self._sound.on()
self._sound.set_surround_sound()
self._sound.set_volume(50)
self._dvd.on()
self._dvd.play(movie)
def end_movie(self) -> None:
self._dvd.stop()
self._dvd.off()
self._sound.off()
self._projector.off()
self._lights.on()
theater = HomeTheaterFacade(DVDPlayer(), Projector(), SoundSystem(),
Lights())
theater.watch_movie("Inception")
theater.end_movie()
Example 3: computer startup and order processing#
A facade can hide a fixed boot sequence or a multi-step workflow with failure paths:
class ComputerFacade:
def __init__(self):
self._cpu = CPU()
self._memory = Memory()
self._hard_drive = HardDrive()
# The boot sequence is hidden behind a "power button"
def start(self) -> None:
self._cpu.freeze()
self._memory.load(0, self._hard_drive.read(0, 1024))
self._cpu.execute()
class OrderFacade:
def __init__(self):
self._inventory = Inventory()
self._payment = Payment()
self._shipping = Shipping()
self._notification = Notification()
# One call coordinates four subsystems, including the failure paths
def place_order(self, product: str, amount: float,
address: str) -> bool:
if not self._inventory.check_stock(product):
return False # out of stock
self._inventory.reserve(product)
if not self._payment.process_payment(amount):
return False # payment failed
self._shipping.ship(product, address)
self._notification.send_email(f"Order confirmed for {product}")
return True
Template#
This is the bare shape of a facade:
class Facade:
def __init__(self):
self._a = SubsystemA()
self._b = SubsystemB()
self._c = SubsystemC()
def simple_operation(self) -> None:
self._a.operation_a()
self._b.operation_b()
self._c.operation_c()
Passing the subsystems into the constructor (as in the ticket-booking and home-theater examples) instead of creating them inside makes the facade easier to test.
Real-world examples#
Facades are common in libraries and service architectures:
requests.get(url)hides connection pooling, sockets, redirects and encoding behind one call.shutil(copytree,make_archive) wraps lower-levelos,zipfileandtarfilecalls.subprocess.runhides the details ofPopen, pipes and waiting for the process.- API gateways give clients one entry point to many microservices.
- A compiler's
compilecommand hides lexer, parser, optimizer and code generator. - Everyday analogies: a restaurant waiter, a travel agent, a bank teller, a universal remote.
When to use it#
A facade helps when:
- A subsystem has many interdependent classes and clients mostly need a few common workflows.
- You want an entry point to a layer in a layered architecture.
- You're wrapping legacy code or a hard-to-use third-party library.
- You want clients decoupled from subsystem details so the subsystem can change freely.
When not to use it#
Skip it when:
- The subsystem is already simple (two or three easy classes), or there's just one class.
- Clients each need fine-grained, different access to the subsystem.
- There are no common call sequences worth packaging.
Pitfalls#
Watch for these traps:
- God object. A facade that grows a method for every possible use becomes a dumping ground. Split it by use case.
- Hiding too much. If clients can't reach features they need, they bypass the facade anyway.
- Missing compensation. Orchestration code must handle partial failures (release the seat if payment fails).
- Business rules in the wrong place. A facade coordinates; core domain rules belong in the subsystem.
Facade vs similar patterns#
The patterns below differ in how many objects they wrap and why:
| Pattern | Shape | Purpose |
|---|---|---|
| Facade | Many → one | Simplify and orchestrate a subsystem |
| Adapter | One → one | Translate one incompatible interface |
| Proxy | One → one, same interface | Control access to one object |
| Mediator | Many ↔ many | Coordinate two-way communication between components; a facade is one-way (client → subsystem) |
| Abstract Factory | n/a | Creates families of objects; a facade simplifies using them |
A common interview question is "Adapter vs Facade?": Adapter changes one interface into the one you expect (translation); Facade gives a group of interfaces one simpler front door (simplification).
Key takeaways#
- A facade is one class with simple methods that drive a complex subsystem.
- Clients depend on the facade, not on every subsystem class.
- It doesn't lock the subsystem away; it just offers an easier path.
- Keep it focused on orchestration, and split it before it becomes a god object.