handbookНаписать

patterns/behavioral/mediator/README.md · тема · 4 языка · 15 тестов

+ практика: форма оформления заказа

Посредник (Mediator) ​

Убирает прямые связи между объектами: каждый говорит только с посредником, а правила их взаимодействия собраны в одном месте.

Проблема ​

Над аэропортом кружат три самолёта, полоса одна. Если Aircraft договариваются между собой, каждый должен знать всех остальных: кто уже садится, кто ждёт, в каком порядке. Три самолёта — шесть связей, десять — уже девяносто. Правило «садится один, остальные ждут в очереди» размазано по всем бортам, и чтобы поменять его (скажем, пропускать вперёд борт с малым остатком топлива), придётся править каждый.

Решение ​

Самолёты не знают друг о друге. Каждый знает только диспетчерскую ControlTower: просит посадку и сообщает, что сел и освободил полосу. Всё остальное решает Tower: кому дать полосу, кого поставить в очередь, кого вызвать следующим.

Связи «каждый с каждым» превращаются в звезду: N объектов — N связей с посредником. Правило взаимодействия живёт в одном классе, и самолёты от его смены не меняются.

Структура ​

asks

clears / holds

«interface»

ControlTower

+requestLanding(Aircraft)

+landed(Aircraft)

Tower

-onRunway: Aircraft

-queue: Aircraft[]

+requestLanding(Aircraft)

+landed(Aircraft)

Aircraft

+callsign: string

+status: Status

+requestLanding()

+land()

+hold()

+clear()

«enumeration»

Status

approaching

holding

cleared

landed

Коллега Aircraft делает две вещи: сообщает посреднику о своих действиях (requestLanding, land) и выполняет его команды (hold, clear). Ссылок на другие самолёты у него нет, только на ControlTower. Статус меняет сам самолёт, но решение о статусе принимает диспетчерская. land() сначала сообщает о посадке и только потом отмечает себя севшим: если диспетчерская откажет, статус не изменится.

Как это собирается ​

Диспетчерские создаёт система управления аэропортами, по одной Tower на аэропорт. Самолёты появляются из расписания прилётов: для каждого рейса создаётся Aircraft, и в конструктор ему передаётся диспетчерская аэропорта назначения. Это единственная связь, которую самолёт получает: о других бортах и о второй диспетчерской он не знает.

В примере два борта летят в SVO, один в LED. LH300 сразу получает полосу, хотя SVO занят: у каждой диспетчерской своя очередь. AF200 пытается сесть без разрешения, NotCleared ловит бортовая система и уходит на второй круг, а состояние диспетчерской не меняется. Когда SU100 освобождает полосу, Tower сам вызывает следующего из очереди.

Сценарий целиком с выводом: пример на PHP, он же на Go, Python и TypeScript в панели кода.

Реализации ​

Tower хранит два куска состояния: кто сейчас на полосе и очередь ожидающих. requestLanding либо отдаёт полосу, либо ставит в конец очереди; landed проверяет, что сел именно тот, кому дали полосу, и вызывает следующего. Самолёт, который садится без разрешения, получает ошибку NotCleared, и состояние диспетчерской не меняется.

ЯзыкОсобенность
PHPСтатус — enum Status, свойство public private(set) читается снаружи, меняется только самолётом; ошибка — NotCleared от LogicException
GoControlTower — интерфейс, Landed возвращает error с сентинелом ErrNotCleared; нулевое значение Tower{} готово к работе
PythonКонтракт — Protocol, статус — StrEnum, очередь — deque
TypeScriptСтатус — объединение строковых литералов, поле #status закрыто и читается через геттер

Запуск: make test T=patterns/behavioral/mediator

Плюсы и минусы ​

Тесты запускают три борта, SU100, AF200 и LH300, и проверяют только статусы самолётов: первый получил полосу, двое ждут; после посадки SU100 полосу получает AF200, а LH300 всё ещё ждёт; AF200, который садится без разрешения, получает ошибку, и ни его статус, ни статус SU100 не меняются. Ни один тест не связывает самолёты напрямую: очередь существует только в диспетчерской.

  • ✅ Коллеги не знают друг о друге: их можно добавлять, убирать и переиспользовать с другим посредником.
  • ✅ Правило взаимодействия в одном месте: приоритет для борта с малым остатком топлива — правка одного Tower.
  • ✅ Связей N вместо N x (N - 1).
  • ❌ Посредник собирает в себе всю логику связей и легко вырастает в огромный класс, который знает всё обо всех.
  • ❌ Поток управления менее нагляден: чтобы понять, почему самолёт перешёл в cleared, надо читать посредника, а не самолёт.

Когда не применять ​

  • Объектов два-три и связи между ними стабильны: прямой вызов понятнее, чем посредник ради посредника.
  • Объекту нужно просто оповестить заинтересованных и не важно, кто и как отреагирует: это Наблюдатель (Observer) или шина событий, а не посредник с правилами.
  • Нужен один упрощённый вход в набор подсистем, которые сами друг с другом не говорят: это Фасад.

Отличия между языками ​

  • PHP — public private(set) (PHP 8.4) даёт читаемый снаружи, но закрытый на запись статус без геттера. Посредник и коллеги ссылаются друг на друга, интерфейс ControlTower разрывает эту зависимость на уровне типов.
  • Go — циклическая ссылка «самолёт -> диспетчерская -> самолёт» не проблема внутри одного пакета, но интерфейс всё равно нужен, чтобы тест мог подставить другую диспетчерскую. Ошибка — значение, а не исключение: Land возвращает её и не меняет статус.
  • Python — Protocol проверяет форму, наследоваться от него Tower не обязан. Аннотация "Aircraft" в кавычках: класс объявлен ниже контракта.
  • TypeScript — implements ControlTower необязателен (структурной типизации хватило бы), но делает роль класса явной. Приватное поле #status закрыто и в рантайме, не только для компилятора.

Практика ​

Учебная диспетчерская обрабатывает два события и ни одно её действие не вызывает новых событий. В форме оформления заказа всё сложнее: посредник сам меняет поля, а поля об этом сообщают ему же, и без защиты уведомления ходят по кругу. Практика: форма оформления заказа показывает CheckoutForm, где способ доставки, страна и промокод связаны только через посредника, и одно изменение пользователя даёт ровно один проход правил.

Связанные темы ​

  • Практика: форма оформления заказа — посредник между полями формы и защита от зацикливания уведомлений
  • Фасад (Facade) — тоже центральный объект, но подсистемы о фасаде не знают, а коллеги посредника знают и сами к нему обращаются.
  • Стратегия (Strategy) — правило выбора следующего борта можно вынести в стратегию и подставлять в Tower.
© 2014-2026 Тимур ТурдыевСайт и справочник работают на VPS от AdminVPS