Structural Pattern Pattern Deep Dive

Facade

Some subsystems are correctly designed but painful to use — many classes, a specific call order, fiddly configuration. The Facade pattern answers a simple question: how do you give client code one simple entry point into a complex subsystem, without dumbing down or removing the subsystem's own detailed API for the callers who still need it?

⚠️ The Problem

Converting a video file correctly might require: picking the right codec class and configuring its bitrate, running an audio-mixing service to normalize volume levels, then calling a metadata writer to embed the title and duration into the output file — each as a separate class, called in a specific order, with settings that have to agree with each other (the codec's frame rate has to match what the metadata writer expects, for instance). Every caller who just wants to "convert this file to MP4" has to know all of that, get the order right, and keep the configuration consistent — technically correct, but a maintenance nightmare copy-pasted across every call site.

✅ The Solution

Add one Facade class with a single high-level method — e.g. convertVideo(file, format) — that internally creates and coordinates the subsystem's classes in the right order with sensible, consistent configuration. Callers who just need "convert this file" call the one facade method and never see the codec, mixer, or metadata writer directly. Callers who need fine-grained control — a custom bitrate, skipping the audio mix — can still reach into the subsystem's classes directly; the facade doesn't remove that access, it just isn't required for the common case.

Example 1 — a video conversion facade

Primary Example — JavaScript
// The subsystem: correct, but each class must be used in a specific order with matching config.
class VideoCodec {
  encode(file, format, bitrateKbps) {
    console.log(`Encoding ${file} to ${format} at ${bitrateKbps}kbps`);
    return `${file}.${format}`;
  }
}

class AudioMixer {
  normalize(file) {
    console.log(`Normalizing audio levels for ${file}`);
  }
}

class MetadataWriter {
  writeMetadata(file, title, durationSeconds) {
    console.log(`Writing metadata to ${file}: title="${title}", duration=${durationSeconds}s`);
  }
}

// The Facade: one simple method, correct call order and config handled internally.
class VideoConverterFacade {
  constructor() {
    this.codec = new VideoCodec();
    this.mixer = new AudioMixer();
    this.metadata = new MetadataWriter();
  }

  convertVideo(file, format, title, durationSeconds) {
    const output = this.codec.encode(file, format, 5000);
    this.mixer.normalize(output);
    this.metadata.writeMetadata(output, title, durationSeconds);
    return output;
  }
}

// Simple case: one call, no need to know the subsystem exists.
const converter = new VideoConverterFacade();
converter.convertVideo("vacation.mov", "mp4", "Summer Vacation", 245);

// Advanced case: a caller who needs a custom bitrate still reaches the subsystem directly.
const codec = new VideoCodec();
codec.encode("vacation.mov", "mp4", 12000);

Most call sites never need to know AudioMixer or MetadataWriter exist — they call convertVideo() and get a correctly-ordered result. The one caller that needs a nonstandard bitrate still can, by talking to VideoCodec directly.

Example 2 — a smart home "good morning" facade

Secondary Example — Go
package smarthome

import "fmt"

// The subsystem: independent services, each with their own API.
type Lights struct{}

func (l Lights) TurnOn(room string) {
	fmt.Printf("Lights: turning on in %s\n", room)
}

type Thermostat struct{}

func (t Thermostat) SetTemperature(celsius int) {
	fmt.Printf("Thermostat: setting to %d°C\n", celsius)
}

type SecuritySystem struct{}

func (s SecuritySystem) Disarm() {
	fmt.Println("Security: disarming")
}

// The Facade: one high-level method coordinating three subsystems in the right order.
type SmartHomeFacade struct {
	lights  Lights
	thermo  Thermostat
	arm     SecuritySystem
}

func NewSmartHomeFacade() *SmartHomeFacade {
	return &SmartHomeFacade{}
}

func (f *SmartHomeFacade) GoodMorning() {
	f.arm.Disarm()
	f.lights.TurnOn("kitchen")
	f.thermo.SetTemperature(21)
}

// Usage: one call replaces manually sequencing three subsystems correctly every morning.
func main() {
	home := NewSmartHomeFacade()
	home.GoodMorning()
}

Anyone who wants fine control can still call Lights, Thermostat, or SecuritySystem directly — SmartHomeFacade just removes the need to remember the right sequence and settings for the routine "good morning" case.

👍 When To Use It

  • A subsystem has multiple classes that must be used together, in a specific order, with coordinated configuration, for the common case to work correctly.
  • You want to decouple client code from a complex or third-party subsystem's internal structure, so the subsystem can be refactored later without breaking every caller.
  • Most callers only need the "normal" use case, and only a minority need fine-grained control over the subsystem's individual pieces.

👎 When NOT To Use It

  • When the subsystem is already simple to use directly — a facade over one or two straightforward classes just adds an indirection layer with no real simplification.
  • When nearly every caller needs fine-grained control anyway — if the "simple case" the facade optimizes for is rare, you've built a shortcut nobody takes.
  • When the facade starts accumulating configuration options for every possible caller's needs — at that point it's becoming a second, parallel API to the subsystem instead of a genuine simplification.

❓ FAQ

Isn't Facade the same as Adapter? Both sit in front of other classes.

Adapter makes one existing interface look like a different single interface a client already expects — it's a one-to-one translation, and the wrapped class typically already does everything the client needs. Facade simplifies access to many classes in a subsystem behind one new, simpler interface — it's about hiding complexity, not translating a mismatched contract, and the facade often introduces behavior (like calling things in a specific order) that didn't exist as a single operation before.

Does using a Facade mean I can never use the subsystem directly again?

No — that's a common misconception. A well-designed facade is an additional, simpler entry point, not a wall. Code that needs capabilities the facade doesn't expose is expected to keep using the subsystem's classes directly; the facade exists purely to spare the common case from that complexity.

Can a codebase have more than one facade over the same subsystem?

Yes, and it's often reasonable — different callers may want different "simple cases." A batch-processing facade and an interactive-preview facade over the same video subsystem, for example, might sequence or configure the same underlying classes differently for their respective use cases.

🙋 There Are No Dumb Questions

Q: Doesn't a Facade just move the complexity into itself, rather than actually removing it?
A: In a sense, yes — the complexity has to live somewhere. The win is that it now lives in exactly one place, written once and tested once, instead of being duplicated (and inconsistently gotten right or wrong) across every call site that needs the common case. The facade concentrates the coordination logic so most callers never have to reason about it at all.

✅ Quick Check

What does a Facade provide that the underlying subsystem doesn't?


Next up: what if your app needs to create hundreds of thousands of similar objects — like every tree in a forest — without exhausting memory? That's the Flyweight pattern.

← Back to
Next →