178 lines
6.6 KiB
Markdown
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)
|