Фасад (Facade)
Даёт один простой вход в набор подсистем: клиент вызывает одну операцию, а порядок шагов и связи между частями знает фасад.
Проблема
Чтобы перевести ролик в mp4, нужны четыре подсистемы: Probe узнаёт кодек исходника, Transcoder перекодирует видео, AudioNormalizer выравнивает громкость, Muxer упаковывает результат в контейнер. Каждая простая, но вместе их надо вызвать в правильном порядке, передать MediaInfo из одной в другую и не забыть, что перекодировать уже подходящий кодек не нужно.
Если это знание живёт в каждом месте, где конвертируют видео (загрузка, админка, фоновая задача), то порядок шагов копируется трижды, а новая подсистема (скажем, водяной знак) требует правки во всех трёх.
Решение
Фасад VideoConverter знает подсистемы и сценарий: convert('talk.mkv', 'mp4') выбирает целевой кодек, опрашивает исходник, перекодирует только при необходимости, нормализует звук и упаковывает. Клиент получает Video и ничего не знает о шагах.
Фасад не прячет подсистемы насовсем: кому нужен только Probe, тот вызывает его напрямую. Фасад — удобный вход для типового сценария, а не стена. И он не добавляет поведения сверх композиции: вся работа делается в подсистемах, фасад только решает, кого и в каком порядке звать.
Структура
Подсистемы ничего не знают о фасаде и друг о друге: каждая принимает MediaInfo и возвращает новый. Вместо настоящей работы с медиа они пишут шаг в Trace: так тест видит, что и в каком порядке вызвал фасад.
Как это собирается
Фасад создаётся один раз, при запуске воркера очереди загрузок: VideoConverter сам собирает Probe, Transcoder, AudioNormalizer и Muxer вокруг общего Trace (в PHP это VideoConverter::create()). Воркер берёт задания из очереди и на каждое делает один вызов convert(). Какие подсистемы нужны, решает фасад: clip.mp4 уже в h264 и проходит без перекодирования, поэтому шагов у него три, а не четыре.
Задание с форматом, которого фасад не умеет, падает до запуска подсистем. Воркер ловит ошибку, помечает задание как неудачное и берёт следующее: одна плохая загрузка не останавливает очередь. Ни одного вызова подсистем в коде воркера нет.
Сценарий целиком с выводом: пример на PHP, он же на Go, Python и TypeScript в панели кода.
Реализации
Фасад держит две вещи: таблицу «формат -> кодек» и сценарий из четырёх вызовов. Проверка формата идёт первой, до любой подсистемы: неподдерживаемый формат не должен запустить даже опрос файла. Условие перед transcode — то знание, ради которого фасад и нужен: клиенту не надо помнить, что webm в webm перекодировать незачем.
| Язык | Особенность |
|---|---|
| PHP | Подсистемы приходят в конструктор, VideoConverter::create($trace) собирает типовой набор; final readonly везде |
| Go | Фасад — структура с неэкспортируемыми полями-подсистемами и NewVideoConverter; ошибки — сентинелы с %w |
| Python | Подсистемы — dataclass, replace() вместо мутации; фасад собирает подсистемы в __init__ |
| TypeScript | MediaInfo — readonly интерфейс, подсистемы возвращают копии через spread; фасад создаёт подсистемы в конструкторе |
Запуск: make test T=patterns/structural/facade
Плюсы и минусы
Тесты проверяют поведение фасада через журнал шагов: mkv в mp4 проходит все четыре подсистемы по порядку; webm в webm пропускает перекодирование; неподдерживаемый формат падает, не тронув ни одной подсистемы. Последний тест — Probe работает и без фасада.
- ✅ Клиентский код вызывает одну операцию и не зависит от состава подсистем.
- ✅ Сценарий записан в одном месте: новый шаг (водяной знак) добавляется в фасад, а не во все места вызова.
- ✅ Подсистемы остаются доступны напрямую для нетиповых задач.
- ❌ Фасад легко раздувается в «божественный объект», который знает всё приложение. Держите его тонким: только порядок и связи.
- ❌ Сценарий один на всех: кому нужен другой порядок, идёт мимо фасада, и правила снова расползаются.
Когда не применять
- Подсистема одна или вызов и так простой: фасад станет обёрткой ради обёртки.
- Нужно не упростить, а сменить интерфейс под чужой контракт: это Адаптер.
- Клиентам нужны разные комбинации шагов: лучше дать им подсистемы и пару небольших функций-помощников, чем фасад с десятком флагов.
Отличия между языками
- PHP — подсистемы приходят в конструктор, поэтому фасад легко собрать с подменами; именованный конструктор
create()закрывает типовую сборку. В фреймворках ту же роль играет сервис из контейнера. - Go — фасадом часто служит сам пакет: экспортируется пара функций, подсистемы остаются неэкспортируемыми. Здесь подсистемы экспортированы, чтобы показать доступ в обход фасада, но поля фасада закрыты.
- Python — модуль с функцией
convert()и есть фасад; класс нужен, когда подсистемы держат общее состояние (здесьTrace). НеизменяемыеMediaInfoиdataclasses.replaceделают передачу между шагами безопасной. - TypeScript — структурная типизация позволяет отдать клиенту только тип
{ convert }, скрыв остальное без отдельного интерфейса. Spread{ ...source, codec }даёт копию вместо мутации.
Практика
Учебный фасад только вызывает подсистемы по порядку, и ни одна из них не падает посреди сценария. В жизни фасад над складом, оплатой и уведомлениями отвечает ещё и за то, что делать, когда шаг не удался. Практика: оформление заказа показывает PlaceOrder, который снимает резерв, если оплата не прошла, и не валит заказ из-за упавшего уведомления.
Связанные темы
- Практика: оформление заказа — фасад, который отвечает за порядок шагов и компенсацию
- Адаптер (Adapter) — тоже обёртка, но меняет интерфейс одного объекта под ожидания клиента, а фасад упрощает вход в несколько.
- Абстрактная фабрика (Abstract Factory) — может создавать подсистемы для фасада, когда их набор зависит от окружения.