Add code examples, and documentation
This commit is contained in:
177
docs/facade.md
Normal file
177
docs/facade.md
Normal file
@@ -0,0 +1,177 @@
|
||||
[← 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)
|
||||
Reference in New Issue
Block a user