Mediator Pattern — Посредник

Топология «звезда» вместо «все со всеми». Объекты не зацепляются напрямую — общаются через медиатор.

Проблема

N объектов, каждый должен общаться с каждым. Полный граф связей — N×(N-1)/2 связей. При N=10 это 45 связей. Сильное зацепление, изменение одного — каскад правок. Аналог в реальной жизни — авиадиспетчер.

Где используется

  • Event bus / pub-sub системы в SPA
  • Управление состоянием чата (кто кому пишет)
  • Координация компонентов UI (модальные окна, уведомления)
  • Архитектура микрофронтендов
  • Redux и подобные state managers (store как медиатор)

Решение

  • Все объекты ссылаются на медиатор, не друг на друга.
  • Медиатор знает обо всех участниках и маршрутизирует сообщения.
  • Связей становится N, а не N²/2.
  • Объекты можно тестировать в изоляции (с моком медиатора).

Реализации

Базовый медиатор

class Mediator {
  participants = new Map();
  register(name, obj) {
    this.participants.set(name, obj);
    obj.mediator = this;
  }
  send(from, to, message) {
    this.participants.get(to)?.receive(from, message);
  }
}

class Component {
  receive(from, message) { console.log(`from ${from}: ${message}`); }
  send(to, message) { this.mediator.send(this.name, to, message); }
}

const med = new Mediator();
const a = new Component(); a.name = 'A';
const b = new Component(); b.name = 'B';
med.register('A', a);
med.register('B', b);
a.send('B', 'hi');

Pub/Sub стиль медиатора

class EventBus {
  constructor { this.handlers = {}; }

  subscribe(event, handler) {
    if (!this.handlers[event]) this.handlers[event] = ;
    this.handlers[event].push(handler);
    return  => {
      this.handlers[event] = this.handlers[event].filter(h => h !== handler);
    };
  }

  publish(event, data) {
    (this.handlers[event] || ).forEach(h => h(data));
  }
}

const bus = new EventBus();
const unsub = bus.subscribe('userLoggedIn', (user) => console.log(`Hi, ${user.name}`));
bus.publish('userLoggedIn', { name: 'Ivan' });
unsub;

Чат-комната

class ChatRoom {
  constructor { this.participants = {}; }

  register(participant) {
    this.participants[participant.name] = participant;
    participant.chatRoom = this;
  }

  send(message, from, to) {
    if (to) {
      this.participants[to]?.receive(message, from);
    } else {
      Object.values(this.participants).forEach(p => {
        if (p.name !== from) p.receive(message, from);
      });
    }
  }
}

class Participant {
  constructor(name) { this.name = name; }
  send(message, to) { this.chatRoom.send(message, this.name, to); }
  receive(message, from) { console.log(`[${this.name}] ${from}: ${message}`); }
}

Где используется в JS-экосистеме

  • Redux store — центральный медиатор между компонентами
  • Event bus в Vue/React (mitt, eventemitter3)
  • NestJS @EventEmitter + handlers — медиаторная топология
  • Chat-app server — клиенты не знают друг о друге, общаются через сервер
  • Air traffic control — классический пример из книги GoF

Подводные камни

  • Mediator → God Object: если медиатор знает всё про всех, становится монстром. Медиатор должен только координировать, не обрабатывать логику.
  • Mediator vs Observer: Mediator знает участников; Observable рассылает всем подписчикам, не знающим друг друга.
  • Mediator vs Bridge: Bridge связывает иерархии наследования; Mediator связывает объекты без знания об иерархиях.
  • Mediator vs Facade: Facade скрывает сложность подсистемы наружу; Mediator связывает объекты между собой внутри.
  • Утечки памяти при подписке — если не отписываться при уничтожении компонента, обработчики накапливаются.
  • Слишком много событий — избыточное количество делает систему трудно отслеживаемой. Используй namespace: 'cart:add', 'cart:remove'.

Главные тезисы автора

  • «Функцию зацепления переносим на медиатор» — суть паттерна.
  • «Объекты не зацеплены друг за друга. Медиатор сам их зацепляет».
  • Топология меняется с полного графа на звезду.
  • Композиция или агрегация в медиаторе — оба валидны.
  • Bridge vs Mediator: Bridge знает об иерархиях, Mediator — нет.
  • На функциональном уровне — это функция, связывающая два контракта.

🎓 Источники

См. также