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

One call that coordinates five subsystemsTry it yourself
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

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 over XMLHttpRequest and 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.