Facade

Intermediate · 10 min read · ▶ live playground · ✦ checkpoint

Movie night starts with five devices and one fragile ritual. Turn on the amplifier, choose the tuner channel, dim the lights, start the projector, lower the screen—and one missed call leaves everyone staring at the wrong input.

The Facade pattern provides one small interface over a more complicated group of objects. A facade is the object that exposes that focused entry point; a subsystem is one of the collaborating classes behind it that still owns its specialized work.

The client learns one operation instead of an orchestration sequence. That convenience costs another layer, and the facade can become an oversized coordinator if every new feature lands there.

Before: every client knows the setup ritual

Each device has a reasonable interface. The coupling appears in the caller, which must know all five objects and the order in which to invoke them:

export {};
 
class Amplifier {
  public on(): void {}
}
 
class Tuner {
  public setChannel(channel: string): void {}
}
 
class Lights {
  public dim(percent: number): void {}
}
 
class Projector {
  public on(): void {}
}
 
class Screen {
  public down(): void {}
}
 
function watchMovie(
  amplifier: Amplifier,
  tuner: Tuner,
  lights: Lights,
  projector: Projector,
  screen: Screen,
  channel: string,
): void {
  // smell: every movie client must reproduce this sequence.
  amplifier.on();
  tuner.setChannel(channel);
  lights.dim(30);
  projector.on();
  screen.down();
}

A mobile controller, voice command, and scheduled automation would each repeat the same knowledge. If the projector must warm up before the screen moves, every caller becomes a migration site. The subsystem classes are not the problem; their workflow has escaped into every client.

After: put one use case behind a facade

HomeTheaterFacade names the user's goal rather than the device-level steps. It holds the subsystem objects and coordinates them, while each device keeps its own behavior.

class diagram

One watchMovie call coordinates the home-theater subsystem

One watchMovie call coordinates the home-theater subsystem
  1. 1HomeTheaterFacade gives movie night one entry point instead of exposing its setup sequence.
  2. 2Client depends on the facade's small surface and no longer coordinates the equipment.
  3. 3The facade asks Amplifier to power on at the right point in the workflow.
  4. 4Tuner keeps channel selection; the facade supplies the channel chosen by the client.
  5. 5Lights owns dimming, while the facade chooses the movie-night setting.
  6. 6Projector remains a focused subsystem class rather than moving its behavior into the facade.
  7. 7Screen completes the sequence; future ordering changes now have one coordination point.

The dashed dependency arrows read “uses.” The facade needs each subsystem to complete watchMovie(), but it does not inherit from them or turn them into one giant device.

export {};
 
class Amplifier {
  public on(): string {
    return "amplifier on";
  }
}
 
class Tuner {
  public setChannel(channel: string): string {
    return `tuner channel ${channel}`;
  }
}
 
class Lights {
  public dim(percent: number): string {
    return `lights ${percent}%`;
  }
}
 
class Projector {
  public on(): string {
    return "projector on";
  }
}
 
class Screen {
  public down(): string {
    return "screen down";
  }
}
 
class HomeTheaterFacade {
  private amplifier: Amplifier;
  private tuner: Tuner;
  private lights: Lights;
  private projector: Projector;
  private screen: Screen;
 
  public constructor(
    amplifier: Amplifier,
    tuner: Tuner,
    lights: Lights,
    projector: Projector,
    screen: Screen,
  ) {
    this.amplifier = amplifier;
    this.tuner = tuner;
    this.lights = lights;
    this.projector = projector;
    this.screen = screen;
  }
 
  public watchMovie(channel: string): string {
    return [
      this.amplifier.on(),
      this.tuner.setChannel(channel),
      this.lights.dim(30),
      this.projector.on(),
      this.screen.down(),
      "movie ready",
    ].join("\n");
  }
}
 
class MovieNight {
  private theater: HomeTheaterFacade;
 
  public constructor(theater: HomeTheaterFacade) {
    this.theater = theater;
  }
 
  public start(): string {
    return this.theater.watchMovie("42");
  }
}
 
const theater = new HomeTheaterFacade(
  new Amplifier(),
  new Tuner(),
  new Lights(),
  new Projector(),
  new Screen(),
);
 
console.log(new MovieNight(theater).start());

amplifier on tuner channel 42 lights 30% projector on screen down movie ready

MovieNight depends on one operation that matches its intent. Device order, default brightness, and warm-up rules can change inside HomeTheaterFacade without teaching that sequence to every client.

The facade does not make the subsystem inaccessible. A diagnostics tool may still call Projector.on() directly when it genuinely needs device-level control. Facade offers a convenient path; it is not an access-control boundary.

Facade beside Adapter

Facade and Adapter both place a new object at a boundary, but their intents differ. Facade simplifies several collaborating interfaces by offering a new, smaller interface. Adapter translates one incompatible interface into a target contract the client already expects.

HomeTheaterFacade.watchMovie() does not pretend to be an Amplifier or convert one device API into another. It gathers a use case that spans several devices. The adapter from the previous lesson changed method names, units, and result shapes for one SDK; this facade coordinates a workflow.

When NOT to use it

A facade that forwards one call to one object without translating, coordinating, or protecting a stable boundary adds a name and a delegation hop for no gain. Let the client call that object directly.

The opposite failure is a god object, a class that gathers unrelated decisions and becomes the destination for every change. If HomeTheaterFacade starts choosing films, managing subscriptions, repairing devices, and storing viewing history, it no longer has one focused reason to change. Split facades by coherent use case and keep subsystem rules with their owning classes, following SRP.

Facades can also hide capabilities that advanced clients need. Do not force every caller through a narrow interface when device-level control is a real requirement. The cost is a second public surface to design, document, wire, and test; pay it when several clients benefit from the same stable workflow.

Checkpoint

Answer all three to mark this lesson complete

Facade reduces the number of objects a client must understand. Next, Flyweight reduces the number of heavyweight objects the program must keep in memory by sharing the state they have in common.

+50 XP on completion