Files
Design-Patterns/docs/facade.md
2026-08-13 22:47:17 -04:00

178 lines
6.6 KiB
Markdown

[← Design patterns index](README.md)
# Facade
**Category:** Structural | **Source:** `patterns/facade/CarEngineFacade/` (+
`CarEngineFacadeRecording/`)
## Problem
Using a complex subsystem directly means the client must know every class,
instantiate them all, and call their methods **in the correct order**. That
sequencing knowledge leaks into — and couples — every piece of business logic
that touches the subsystem.
## Solution
Add one class that owns the wiring and the call sequence, exposing a couple of
high-level methods. The subsystem classes stay completely unaware the facade
exists. A facade adds **no new functionality**; it only hides complexity,
deliberately trading away some of the subsystem's generality for ease of use.
## Participants (GoF names → this repo)
- **Facade** → `Facade` — knows which subsystem class handles what, and in what
order.
- **Subsystem classes** → `AirFlowController`, `FuelInjector`, `Starter`,
`CoolingController`, `CatalyticConverter` — do the work, hold no reference to
the facade.
```mermaid
classDiagram
class Facade {
-AirFlowController _air
-FuelInjector _fuel
-Starter _start
-CoolingController _cool
-CatalyticConverter _cat
+Start()
+Stop()
}
Program --> Facade : only dependency
Facade --> AirFlowController
Facade --> FuelInjector
Facade --> Starter
Facade --> CoolingController
Facade --> CatalyticConverter
```
Note every arrow points _away_ from the facade. Nothing points back — that
one-directionality is what distinguishes Facade from Mediator.
## Code
```csharp
// The five subsystem classes — real project has empty "// implementation" bodies
public class AirFlowController { public void TakeAir() {} public void Off() {} }
public class FuelInjector { public void On() {} public void Inject() {} public void Off() {} }
public class Starter { public void Start() {} }
public class CoolingController { public void Run() {} public void Stop() {} public void Cool() {} }
public class CatalyticConverter { public void On() {} public void Off() {} }
```
```csharp
// Facade.cs — owns construction AND ordering
public class Facade
{
private readonly AirFlowController _air = new();
private readonly CatalyticConverter _cat = new();
private readonly CoolingController _cool = new();
private readonly FuelInjector _fuel = new();
private readonly Starter _start = new();
public void Start()
{
_air.TakeAir(); // order matters: air before fuel before spark
_fuel.On();
_fuel.Inject();
_start.Start();
_cool.Run();
_cat.On();
}
public void Stop()
{
_fuel.Off(); // NOT the reverse of Start() — shutdown has its own order
_cat.Off();
_cool.Cool(); // keep cooling after fuel is cut, then stop cooling
_cool.Stop();
_air.Off();
}
}
```
```csharp
// Program.cs — the file keeps the "before" version commented out for comparison
// WITHOUT the facade — client owns construction AND sequencing:
// var airflow = new AirFlowController();
// var fuel = new FuelInjector();
// var start = new Starter();
// var cool = new CoolingController();
// var cat = new CatalyticConverter();
// airflow.TakeAir(); fuel.On(); fuel.Inject(); start.Start(); cool.Run(); cat.On();
// fuel.Off(); cat.Off(); cool.Cool(); cool.Stop(); airflow.Off();
// WITH the facade — 11 ordered calls across 5 types collapse to 2 calls on 1 type:
var facade = new Facade();
facade.Start();
facade.Stop();
```
That the `Stop()` sequence is _not_ `Start()` reversed is the point of the whole
example: the ordering is real domain knowledge, it is easy to get wrong, and it
should live in exactly one place.
## Output (verified)
**None** — the subsystem method bodies are deliberately empty stubs, so the
program runs and exits silently. This example is about the _shape of the
dependencies_, not runtime behavior.
## GoF pseudocode (video conversion library)
```
class VideoConverter is
method convert(filename, format): File is
file = new VideoFile(filename)
sourceCodec = new CodecFactory.extract(file)
if (format == "mp4") destinationCodec = new MPEG4CompressionCodec()
else destinationCodec = new OggCompressionCodec()
buffer = BitrateReader.read(filename, sourceCodec)
result = BitrateReader.convert(buffer, destinationCodec)
result = (new AudioMixer()).fix(result)
return new File(result)
convertor = new VideoConverter()
mp4 = convertor.convert("youtubevideo.ogg", "mp4")
```
## Applicability
- You want a simple, limited interface to a complex subsystem that satisfies
most clients; the few needing more can still reach past the facade.
- There are many dependencies between clients and a subsystem's implementation
classes.
- You want to **layer** subsystems — give each layer a facade and let layers
talk only through them.
## Pros / Cons
| Pros | Cons |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Isolates client code from subsystem complexity | Can grow into a god object coupled to everything |
| Reduces the number of objects clients deal with; cuts compile-time dependencies | Adds an indirection layer that must be maintained |
| Doesn't _prevent_ direct subsystem access when a client needs it | A facade that grows too large needs splitting into refined facades |
## Seen in the wild (.NET)
- `HttpClient` over the sockets/handler/header stack.
- `File.ReadAllText` over `FileStream` + `StreamReader` + encoding detection.
- `WebApplication.CreateBuilder` over the hosting/DI/config subsystems.
## Related patterns
- **Adapter** makes an _existing_ interface usable; Facade defines a _new_,
simpler one. Adapter usually wraps one object, Facade a whole subsystem.
- **Facade vs. Mediator** — both organize collaboration among many classes.
Facade is **one-directional** and the subsystem doesn't know it exists;
Mediator is **multi-directional** and colleagues explicitly depend on it.
- **Abstract Factory** is the alternative when you only want to hide subsystem
_object creation_.
- A facade is often made a **Singleton**, since one instance usually suffices.
---
**← Prev:** [Decorator](decorator.md) · [All patterns](README.md) · **Next →**
[Mediator](mediator.md)