designpattern.site

Facade Pattern

Provide a simplified, unified interface to a complex subsystem, shielding clients from its internal details.

Overview

Real systems are rarely simple under the hood. A home theater has an amplifier, a projector, a Blu-ray player, streaming hardware, and lighting controls — each with its own API. An e-commerce checkout touches inventory, payment, shipping, and notification services, each with a different protocol.

The Facade pattern is a structural design pattern that introduces a single, high-level interface that wraps a group of related interfaces inside a subsystem. Callers talk to the Facade; the Facade delegates to the right components in the right order. The subsystem components themselves are unchanged — they remain fully available for clients that need fine-grained control.

The name comes from architecture: a building's facade is the front face presented to the street, hiding the maze of pipes, wires, and structural beams behind it. The Facade pattern does the same for software.

Problem & Motivation

Imagine you are building an e-commerce platform. Placing an order involves:

  1. Checking that all items are in stock (InventoryService)
  2. Charging the customer (PaymentService)
  3. Creating a shipment (ShippingService)
  4. Sending a confirmation email (NotificationService)

If every controller or API handler orchestrates these four services directly, you end up with the same four-step sequence duplicated across your codebase. Any change to the order — say, adding a fraud check between payment and shipping — requires touching every call site. Tests must mock all four services even when they are only testing unrelated behavior.

The naive alternative — one giant "God object" that does everything — is worse. It has too many responsibilities and is impossible to replace or test in parts.

A Facade sits between the callers and the subsystem. It exposes a single placeOrder() method. Callers know nothing about inventory, payment, shipping, or notifications — they just call placeOrder() and trust the result.

Class Diagram

Loading diagram...

The key structural points are:

  • OrderFacade is the only class clients depend on. It holds references to each subsystem service.
  • The four subsystem classes (InventoryService, PaymentService, ShippingService, NotificationService) are independent of each other and of the Facade — they do not know the Facade exists.
  • Clients that need low-level control can still bypass the Facade and call subsystem services directly.

Implementation

All four implementations model the same scenario: an OrderFacade that orchestrates inventory, payment, shipping, and notification to place an order in a single method call.

// Subsystem classes
class InventoryService {
  checkStock(items: string[]): boolean {
    console.log(`Checking stock for: ${items.join(", ")}`);
    return true; // assume in stock
  }

  reserveItems(items: string[]): void {
    console.log(`Reserved: ${items.join(", ")}`);
  }
}

class PaymentService {
  charge(customerId: string, amount: number): boolean {
    console.log(`Charging customer ${customerId} $${amount.toFixed(2)}`);
    return true; // assume success
  }
}

class ShippingService {
  createShipment(orderId: string, items: string[]): string {
    const trackingId = `TRACK-${orderId.toUpperCase()}`;
    console.log(`Shipment created: ${trackingId}`);
    return trackingId;
  }
}

class NotificationService {
  sendConfirmation(email: string, orderId: string, trackingId: string): void {
    console.log(`Confirmation sent to ${email} for order ${orderId} (${trackingId})`);
  }
}

// Facade
interface Order {
  id: string;
  customerId: string;
  email: string;
  items: string[];
  totalAmount: number;
}

class OrderFacade {
  private inventory = new InventoryService();
  private payment = new PaymentService();
  private shipping = new ShippingService();
  private notification = new NotificationService();

  placeOrder(order: Order): boolean {
    console.log(`\n-- Placing order ${order.id} --`);

    if (!this.inventory.checkStock(order.items)) {
      console.log("Order failed: items out of stock");
      return false;
    }

    if (!this.payment.charge(order.customerId, order.totalAmount)) {
      console.log("Order failed: payment declined");
      return false;
    }

    this.inventory.reserveItems(order.items);
    const trackingId = this.shipping.createShipment(order.id, order.items);
    this.notification.sendConfirmation(order.email, order.id, trackingId);

    console.log(`Order ${order.id} placed successfully`);
    return true;
  }
}

// Client code — knows nothing about the subsystem
const facade = new OrderFacade();
facade.placeOrder({
  id: "ord-001",
  customerId: "cust-42",
  email: "alice@example.com",
  items: ["Widget A", "Widget B"],
  totalAmount: 59.99,
});

Facade vs. Encapsulation

A Facade does not hide subsystem classes from the rest of the codebase — they remain importable and usable directly. Its purpose is to provide a convenient default path, not to enforce access control. If you need to prevent callers from reaching subsystem internals entirely, enforce that through package visibility (Go unexported types, Java package-private, Python leading underscore conventions) rather than the Facade pattern alone.

Real-World Examples

Browser Fetch API — the fetch() function is a facade over the browser's networking stack. Under the hood it manages HTTP connections, redirects, cookies, CORS preflight requests, and response parsing. You call fetch(url) and get a Promise back; the browser's subsystem complexity is completely invisible.

ORM query builders — calling User.findAll({ where: { active: true } }) in Sequelize or User.objects.filter(active=True) in Django triggers connection pooling, SQL generation, parameter binding, query execution, and result hydration. Each of those is a separate subsystem component; the ORM's model class is the Facade you actually interact with.

AWS SDK high-level clients — the AWS SDK exposes simple method calls like s3.upload(params) that internally handle multipart uploads, retry logic, credential refresh, request signing, and regional endpoint resolution. The complexity of the S3 protocol is behind a Facade surface area that fits in one screen.

Pros and Cons

Advantages

Reduced coupling — client code depends only on the Facade interface, not on the individual subsystem classes. You can restructure or replace subsystem internals without touching callers.

Simpler onboarding — new team members learn one Facade method rather than the orchestration sequence across N services. The common case becomes a one-liner.

Centralized change point — when the business process changes (add a fraud check, swap the payment provider), there is one place to update rather than hunting through every call site.

Testability of the common path — integration tests can exercise the full order flow by calling one method and asserting one outcome, rather than manually wiring up four service calls.

Disadvantages

Risk of becoming a God object — if you keep adding convenience methods to the Facade, it accumulates responsibilities and turns into the monolith you were trying to avoid. Keep each Facade focused on one workflow or subsystem boundary.

Leaky abstraction — if callers need to handle every failure mode from every subsystem service, the Facade's simplification is illusory. The error surface area bleeds through, and callers end up coupling to internals anyway.

Hidden performance costs — the Facade may do more work than callers expect. A single placeOrder() call fires four network requests. If callers need to batch or parallelize, the sequential Facade becomes a bottleneck.

Subsystem access still needed — advanced use cases often require calling subsystem services directly. If your Facade is the only documented entry point, developers have to dig through the source to discover the lower-level API.

When to Use / When to Avoid

Use Facade when:

  • You want to provide a simple interface to a complex or deeply layered subsystem.
  • You want to layer your application and the facade serves as an entry point to each layer (presentation → application → domain → infrastructure).
  • You are wrapping a third-party library whose API is verbose or inconsistent, and you want to insulate your codebase from it.
  • Multiple call sites repeat the same orchestration sequence — extract it into a Facade once.

Avoid Facade when:

  • The subsystem is already simple — adding a Facade creates indirection with no payoff.
  • Callers routinely need to mix and match subsystem calls in different orders — a single orchestration method will not fit, and you will end up with a Facade that has dozens of overloads.
  • You are using it purely to hide poor subsystem design rather than to simplify a legitimately complex one. Fix the subsystem first.
  • Performance is critical and the abstraction prevents callers from batching, parallelizing, or short-circuiting operations.

Adapter — converts an existing interface to the one a client expects, typically for a single class. A Facade simplifies access to a group of classes. If you are wrapping one external library class, Adapter is the right tool; if you are wrapping a whole external library, use Facade.

Mediator — like Facade, Mediator sits in front of a set of components. The difference is direction: a Facade is a one-way simplification layer (client → Facade → subsystem). A Mediator coordinates two-way communication between components that do not know about each other.

Abstract Factory — often used together with Facade. The Facade can use an Abstract Factory to create subsystem objects, making it possible to swap the entire subsystem for a different implementation (e.g., a test double) at construction time.

Proxy — a Proxy has the same interface as the object it wraps and adds a single cross-cutting concern (caching, access control, logging). A Facade changes the interface and may wrap multiple objects. They are often confused but serve different purposes.

Design Tip

Think of Facade as the published API of a subsystem module. Define it at module boundaries — the interface that the rest of the application is allowed to depend on. Keep subsystem internals package-private (or otherwise unexported) where the language allows, and let the Facade be the only stable surface. This naturally prevents the "Facade is ignored and everyone calls subsystem directly" failure mode.

On this page