Marcelo Pastorino, Software Developer

Facade Pattern

A structural design pattern that exposes a single, simplified interface over a complex subsystem, hiding how its parts cooperate.

The facade pattern is a structural design pattern that provides a unified, higher-level interface to a set of interfaces in a subsystem. It defines a single entry point that coordinates the subsystem’s classes on behalf of the client, so callers do not need to know which classes to use, in what order, or with what preconditions.

The problem it solves

A useful feature often spans several services, such as inventory, billing, notifications, and shipping, each with its own API, error model, and sequencing rules. When every caller orchestrates those steps itself, the same coordination logic spreads across the codebase, and a change to one subsystem forces edits everywhere that touched it.

The facade pattern moves that orchestration behind one class. The client calls a small, intention-revealing method on the facade, and the facade invokes the right subsystem operations in the right order. Subsystem classes stay unchanged; only the facade knows how they fit together.

Structure

  • Facade, the simplified interface the client uses; coordinates calls into the subsystem.
  • Subsystem classes, existing classes that do the real work; they may depend on one another.
  • Client, depends only on the facade, not on individual subsystem types.
Facade as a single entry point into a subsystem

In code

Placing an order touches inventory, payment, and shipping. A facade exposes one placeOrder method; the client never sequences those services itself.

// Subsystem classes: each does one job with its own interface
class InventoryService {
  reserve(items) {
    console.log(`Reserved ${items.length} item(s)`);
  }
}

class PaymentService {
  charge(amountInCents) {
    console.log(`Charged ${amountInCents} cents`);
  }
}

class ShippingService {
  dispatch(address, items) {
    console.log(`Dispatched ${items.length} item(s) to ${address}`);
  }
}

// Facade: one method that orchestrates the subsystem
class OrderFacade {
  constructor(inventory, payment, shipping) {
    this.inventory = inventory;
    this.payment = payment;
    this.shipping = shipping;
  }
  placeOrder({ items, totalInCents, address }) {
    this.inventory.reserve(items);
    this.payment.charge(totalInCents);
    this.shipping.dispatch(address, items);
  }
}

// Client depends only on the facade
const facade = new OrderFacade(
  new InventoryService(),
  new PaymentService(),
  new ShippingService()
);
facade.placeOrder({
  items: ["widget"],
  totalInCents: 4999,
  address: "1 Main St",
});
// Subsystem classes: each does one job with its own interface
class InventoryService {
  reserve(items: string[]): void {
    console.log(`Reserved ${items.length} item(s)`);
  }
}

class PaymentService {
  charge(amountInCents: number): void {
    console.log(`Charged ${amountInCents} cents`);
  }
}

class ShippingService {
  dispatch(address: string, items: string[]): void {
    console.log(`Dispatched ${items.length} item(s) to ${address}`);
  }
}

interface OrderRequest {
  items: string[];
  totalInCents: number;
  address: string;
}

// Facade: one method that orchestrates the subsystem
class OrderFacade {
  constructor(
    private readonly inventory: InventoryService,
    private readonly payment: PaymentService,
    private readonly shipping: ShippingService
  ) {}
  placeOrder(order: OrderRequest): void {
    this.inventory.reserve(order.items);
    this.payment.charge(order.totalInCents);
    this.shipping.dispatch(order.address, order.items);
  }
}

// Client depends only on the facade
const facade = new OrderFacade(
  new InventoryService(),
  new PaymentService(),
  new ShippingService()
);
facade.placeOrder({
  items: ["widget"],
  totalInCents: 4999,
  address: "1 Main St",
});
// Subsystem classes: each does one job with its own interface
public class InventoryService
{
    public void Reserve(IReadOnlyList<string> items) =>
        Console.WriteLine($"Reserved {items.Count} item(s)");
}

public class PaymentService
{
    public void Charge(int amountInCents) =>
        Console.WriteLine($"Charged {amountInCents} cents");
}

public class ShippingService
{
    public void Dispatch(string address, IReadOnlyList<string> items) =>
        Console.WriteLine($"Dispatched {items.Count} item(s) to {address}");
}

public record OrderRequest(
    IReadOnlyList<string> Items,
    int TotalInCents,
    string Address);

// Facade: one method that orchestrates the subsystem
public class OrderFacade
{
    private readonly InventoryService _inventory;
    private readonly PaymentService _payment;
    private readonly ShippingService _shipping;

    public OrderFacade(
        InventoryService inventory,
        PaymentService payment,
        ShippingService shipping)
    {
        _inventory = inventory;
        _payment = payment;
        _shipping = shipping;
    }

    public void PlaceOrder(OrderRequest order)
    {
        _inventory.Reserve(order.Items);
        _payment.Charge(order.TotalInCents);
        _shipping.Dispatch(order.Address, order.Items);
    }
}

// Client depends only on the facade
var facade = new OrderFacade(
    new InventoryService(),
    new PaymentService(),
    new ShippingService());
facade.PlaceOrder(new OrderRequest(
    new[] { "widget" },
    4999,
    "1 Main St"));
# Subsystem classes: each does one job with its own interface
class InventoryService:
    def reserve(self, items: list[str]) -> None:
        print(f"Reserved {len(items)} item(s)")

class PaymentService:
    def charge(self, amount_in_cents: int) -> None:
        print(f"Charged {amount_in_cents} cents")

class ShippingService:
    def dispatch(self, address: str, items: list[str]) -> None:
        print(f"Dispatched {len(items)} item(s) to {address}")

# Facade: one method that orchestrates the subsystem
class OrderFacade:
    def __init__(
        self,
        inventory: InventoryService,
        payment: PaymentService,
        shipping: ShippingService,
    ) -> None:
        self._inventory = inventory
        self._payment = payment
        self._shipping = shipping

    def place_order(self, items: list[str], total_in_cents: int, address: str) -> None:
        self._inventory.reserve(items)
        self._payment.charge(total_in_cents)
        self._shipping.dispatch(address, items)

# Client depends only on the facade
facade = OrderFacade(InventoryService(), PaymentService(), ShippingService())
facade.place_order(["widget"], 4999, "1 Main St")

Relationship to SOLID

Facade supports several SOLID principles. It applies the single-responsibility principle by keeping orchestration in the facade and leaving each subsystem focused on its own concern. It also supports the interface segregation principle because clients see a narrow, task-oriented API instead of every low-level subsystem method. When the facade is expressed as an interface, it follows the dependency inversion principle by letting callers depend on that abstraction rather than on concrete subsystem types.

Facade vs alternatives

  • Adapter pattern, an adapter makes one existing interface match another the client already expects; a facade defines a new, simpler interface over many classes. Adapter translates; facade simplifies and coordinates.
  • Mediator, a mediator centralizes complex, many-to-many communication between peers; a facade is a one-way entry point that hides a subsystem from the client. Mediator reduces coupling between colleagues; facade reduces what the client must know.
  • Decorator pattern, keeps the same interface and adds behavior around one object; a facade wraps several objects and exposes a different, higher-level API.
  • API gateway, at the system boundary, an API gateway is often a practical facade over multiple backend services, routing and aggregating calls behind one public HTTP surface.

Trade-offs

  • The facade can become a god object if it absorbs business rules that belong in domain logic; it should coordinate, not replace, the subsystem’s responsibilities.
  • Hiding complexity helps most clients, but power users or debugging may still need direct access to subsystem classes. A facade does not have to be the only way in.
  • Adds a layer of indirection; when the subsystem is already a single class with a clear API, a facade buys little.
  • Best when several classes must be used together in a stable sequence, when you want to decouple client code from subsystem churn, or when you need a coarse-grained API at a module or service boundary.

See also

  • Adapter Pattern, A structural design pattern that wraps an incompatible interface in one the client expects, letting otherwise unrelated types work together.
  • Composite Pattern, A structural design pattern that models tree structures so clients treat individual objects and groups of objects the same way through a shared component interface.
  • Decorator Pattern, A structural design pattern that wraps an object to add behavior while keeping the same interface, so responsibilities can be composed in layers at runtime.
  • SOLID Principles, Five object-oriented design principles for maintainable, change-tolerant code.

On Wikipedia