Lapa Knowledge Base
Поведенческие паттерны

Mediator

Паттерн Mediator

Mediator — это поведенческий паттерн проектирования, который позволяет уменьшить связанность множества классов между собой, благодаря перемещению этих связей в один класс-посредник.

Проблема

Представьте, что вы создаете диалог для создания пользователя. Диалог состоит из различных элементов управления: текстовые поля для имени и фамилии, чекбокс "Я согласен с условиями", кнопка "Создать" и кнопка "Отмена".

Когда пользователь вводит имя и фамилию, нужно проверить, заполнены ли оба поля, и только тогда активировать кнопку "Создать". Когда пользователь снимает галочку с чекбокса, нужно деактивировать кнопку "Создать".

Если вы добавите эту логику прямо в классы элементов управления, они станут тесно связанными друг с другом, что затруднит их переиспользование.

Решение

Паттерн Mediator предлагает создать объект-посредник, который будет обрабатывать взаимодействие между компонентами системы. Вместо прямого общения компоненты будут общаться через посредника.


Структура

Mediator
└── notify(sender: Component, event: string): void

ConcreteMediator
├── components: Component[]
├── notify(sender: Component, event: string): void
└── addComponent(component: Component): void

Component
├── mediator: Mediator
├── setMediator(mediator: Mediator): void
└── notify(event: string): void

ConcreteComponent
├── mediator: Mediator
├── setMediator(mediator: Mediator): void
└── notify(event: string): void

Пример реализации

JavaScript

// Интерфейс посредника
class Mediator {
    notify(sender, event) {
        throw new Error('Метод notify должен быть переопределен');
    }
}

// Интерфейс компонента
class Component {
    constructor() {
        this.mediator = null;
    }
    
    setMediator(mediator) {
        this.mediator = mediator;
    }
    
    notify(event) {
        if (this.mediator) {
            this.mediator.notify(this, event);
        }
    }
}

// Конкретный посредник - диалог создания пользователя
class UserCreationDialogMediator extends Mediator {
    constructor() {
        super();
        this.firstNameField = null;
        this.lastNameField = null;
        this.agreeCheckbox = null;
        this.createButton = null;
        this.cancelButton = null;
    }
    
    setComponents(firstNameField, lastNameField, agreeCheckbox, createButton, cancelButton) {
        this.firstNameField = firstNameField;
        this.lastNameField = lastNameField;
        this.agreeCheckbox = agreeCheckbox;
        this.createButton = createButton;
        this.cancelButton = cancelButton;
    }
    
    notify(sender, event) {
        switch (event) {
            case 'firstNameChanged':
                this.checkFormValidity();
                break;
            case 'lastNameChanged':
                this.checkFormValidity();
                break;
            case 'agreeChanged':
                this.checkFormValidity();
                break;
            case 'createClicked':
                this.createUser();
                break;
            case 'cancelClicked':
                this.cancelCreation();
                break;
        }
    }
    
    checkFormValidity() {
        const firstNameValid = this.firstNameField && this.firstNameField.getValue().trim() !== '';
        const lastNameValid = this.lastNameField && this.lastNameField.getValue().trim() !== '';
        const agreeChecked = this.agreeCheckbox && this.agreeCheckbox.isChecked();
        
        const isValid = firstNameValid && lastNameValid && agreeChecked;
        
        if (this.createButton) {
            this.createButton.setEnabled(isValid);
        }
        
        console.log(`Форма ${isValid ? 'валидна' : 'невалидна'}`);
    }
    
    createUser() {
        const firstName = this.firstNameField.getValue();
        const lastName = this.lastNameField.getValue();
        
        console.log(`Создание пользователя: ${firstName} ${lastName}`);
        console.log('Пользователь создан успешно!');
        
        // Очищаем форму
        this.firstNameField.setValue('');
        this.lastNameField.setValue('');
        this.agreeCheckbox.setChecked(false);
        this.createButton.setEnabled(false);
    }
    
    cancelCreation() {
        console.log('Отмена создания пользователя');
        
        // Очищаем форму
        this.firstNameField.setValue('');
        this.lastNameField.setValue('');
        this.agreeCheckbox.setChecked(false);
        this.createButton.setEnabled(false);
    }
}

// Конкретные компоненты
class TextField extends Component {
    constructor(name) {
        super();
        this.name = name;
        this.value = '';
    }
    
    setValue(value) {
        this.value = value;
        console.log(`${this.name}: значение изменено на "${value}"`);
        this.notify('valueChanged');
    }
    
    getValue() {
        return this.value;
    }
    
    // Симуляция ввода пользователя
    simulateInput(value) {
        this.setValue(value);
        this.notify(`${this.name.toLowerCase()}Changed`);
    }
}

class Checkbox extends Component {
    constructor(name) {
        super();
        this.name = name;
        this.checked = false;
    }
    
    setChecked(checked) {
        this.checked = checked;
        console.log(`${this.name}: ${checked ? 'отмечен' : 'снят'}`);
        this.notify('checkedChanged');
    }
    
    isChecked() {
        return this.checked;
    }
    
    // Симуляция клика пользователя
    simulateClick() {
        this.setChecked(!this.checked);
        this.notify('agreeChanged');
    }
}

class Button extends Component {
    constructor(name) {
        super();
        this.name = name;
        this.enabled = false;
    }
    
    setEnabled(enabled) {
        this.enabled = enabled;
        console.log(`${this.name}: ${enabled ? 'активирована' : 'деактивирована'}`);
    }
    
    isEnabled() {
        return this.enabled;
    }
    
    // Симуляция клика пользователя
    simulateClick() {
        if (this.enabled) {
            console.log(`${this.name}: клик`);
            this.notify(`${this.name.toLowerCase()}Clicked`);
        } else {
            console.log(`${this.name}: клик заблокирован (кнопка неактивна)`);
        }
    }
}

// Использование
const mediator = new UserCreationDialogMediator();

// Создаем компоненты
const firstNameField = new TextField('Имя');
const lastNameField = new TextField('Фамилия');
const agreeCheckbox = new Checkbox('Согласие');
const createButton = new Button('Создать');
const cancelButton = new Button('Отмена');

// Устанавливаем посредника для всех компонентов
firstNameField.setMediator(mediator);
lastNameField.setMediator(mediator);
agreeCheckbox.setMediator(mediator);
createButton.setMediator(mediator);
cancelButton.setMediator(mediator);

// Настраиваем посредника
mediator.setComponents(firstNameField, lastNameField, agreeCheckbox, createButton, cancelButton);

console.log('=== Тестирование диалога создания пользователя ===');

// Симулируем действия пользователя
firstNameField.simulateInput('Иван');
lastNameField.simulateInput('Петров');
agreeCheckbox.simulateClick();

console.log('\n--- Попытка создать пользователя ---');
createButton.simulateClick();

console.log('\n--- Отмена создания ---');
cancelButton.simulateClick();

console.log('\n--- Попытка создать без согласия ---');
firstNameField.simulateInput('Мария');
lastNameField.simulateInput('Сидорова');
createButton.simulateClick(); // Должна быть заблокирована

TypeScript с интерфейсами

// Интерфейс посредника
interface Mediator {
    notify(sender: Component, event: string): void;
}

// Интерфейс компонента
interface Component {
    setMediator(mediator: Mediator): void;
    notify(event: string): void;
}

// Конкретный посредник - система управления воздушным движением
class AirTrafficControlMediator implements Mediator {
    private aircrafts: Aircraft[] = [];
    private runways: Runway[] = [];
    
    addAircraft(aircraft: Aircraft): void {
        this.aircrafts.push(aircraft);
        aircraft.setMediator(this);
    }
    
    addRunway(runway: Runway): void {
        this.runways.push(runway);
        runway.setMediator(this);
    }
    
    notify(sender: Component, event: string): void {
        if (sender instanceof Aircraft) {
            this.handleAircraftEvent(sender, event);
        } else if (sender instanceof Runway) {
            this.handleRunwayEvent(sender, event);
        }
    }
    
    private handleAircraftEvent(aircraft: Aircraft, event: string): void {
        switch (event) {
            case 'requestLanding':
                this.handleLandingRequest(aircraft);
                break;
            case 'requestTakeoff':
                this.handleTakeoffRequest(aircraft);
                break;
            case 'landingComplete':
                this.handleLandingComplete(aircraft);
                break;
            case 'takeoffComplete':
                this.handleTakeoffComplete(aircraft);
                break;
        }
    }
    
    private handleRunwayEvent(runway: Runway, event: string): void {
        switch (event) {
            case 'runwayAvailable':
                this.handleRunwayAvailable(runway);
                break;
            case 'runwayOccupied':
                this.handleRunwayOccupied(runway);
                break;
        }
    }
    
    private handleLandingRequest(aircraft: Aircraft): void {
        const availableRunway = this.runways.find(runway => runway.isAvailable());
        
        if (availableRunway) {
            console.log(`Разрешение на посадку для ${aircraft.getName()} на полосу ${availableRunway.getId()}`);
            availableRunway.assignAircraft(aircraft);
            aircraft.receiveLandingPermission(availableRunway);
        } else {
            console.log(`Нет свободных полос для ${aircraft.getName()}. Ожидание...`);
            aircraft.receiveWaitingInstruction();
        }
    }
    
    private handleTakeoffRequest(aircraft: Aircraft): void {
        const availableRunway = this.runways.find(runway => runway.isAvailable());
        
        if (availableRunway) {
            console.log(`Разрешение на взлет для ${aircraft.getName()} с полосы ${availableRunway.getId()}`);
            availableRunway.assignAircraft(aircraft);
            aircraft.receiveTakeoffPermission(availableRunway);
        } else {
            console.log(`Нет свободных полос для ${aircraft.getName()}. Ожидание...`);
            aircraft.receiveWaitingInstruction();
        }
    }
    
    private handleLandingComplete(aircraft: Aircraft): void {
        console.log(`Посадка ${aircraft.getName()} завершена`);
        // Освобождаем полосу
        const runway = this.runways.find(r => r.getAssignedAircraft() === aircraft);
        if (runway) {
            runway.releaseAircraft();
        }
    }
    
    private handleTakeoffComplete(aircraft: Aircraft): void {
        console.log(`Взлет ${aircraft.getName()} завершен`);
        // Освобождаем полосу
        const runway = this.runways.find(r => r.getAssignedAircraft() === aircraft);
        if (runway) {
            runway.releaseAircraft();
        }
    }
    
    private handleRunwayAvailable(runway: Runway): void {
        console.log(`Полоса ${runway.getId()} свободна`);
    }
    
    private handleRunwayOccupied(runway: Runway): void {
        console.log(`Полоса ${runway.getId()} занята`);
    }
}

// Конкретные компоненты
class Aircraft implements Component {
    private mediator: Mediator | null = null;
    private name: string;
    private status: string = 'flying';
    
    constructor(name: string) {
        this.name = name;
    }
    
    setMediator(mediator: Mediator): void {
        this.mediator = mediator;
    }
    
    notify(event: string): void {
        if (this.mediator) {
            this.mediator.notify(this, event);
        }
    }
    
    getName(): string {
        return this.name;
    }
    
    requestLanding(): void {
        console.log(`${this.name} запрашивает разрешение на посадку`);
        this.notify('requestLanding');
    }
    
    requestTakeoff(): void {
        console.log(`${this.name} запрашивает разрешение на взлет`);
        this.notify('requestTakeoff');
    }
    
    receiveLandingPermission(runway: Runway): void {
        console.log(`${this.name} получил разрешение на посадку на полосу ${runway.getId()}`);
        this.status = 'landing';
        // Симулируем посадку
        setTimeout(() => {
            this.status = 'landed';
            this.notify('landingComplete');
        }, 1000);
    }
    
    receiveTakeoffPermission(runway: Runway): void {
        console.log(`${this.name} получил разрешение на взлет с полосы ${runway.getId()}`);
        this.status = 'taking_off';
        // Симулируем взлет
        setTimeout(() => {
            this.status = 'flying';
            this.notify('takeoffComplete');
        }, 1000);
    }
    
    receiveWaitingInstruction(): void {
        console.log(`${this.name} получил инструкцию ожидать`);
    }
}

class Runway implements Component {
    private mediator: Mediator | null = null;
    private id: string;
    private assignedAircraft: Aircraft | null = null;
    
    constructor(id: string) {
        this.id = id;
    }
    
    setMediator(mediator: Mediator): void {
        this.mediator = mediator;
    }
    
    notify(event: string): void {
        if (this.mediator) {
            this.mediator.notify(this, event);
        }
    }
    
    getId(): string {
        return this.id;
    }
    
    isAvailable(): boolean {
        return this.assignedAircraft === null;
    }
    
    assignAircraft(aircraft: Aircraft): void {
        this.assignedAircraft = aircraft;
        console.log(`Полоса ${this.id} назначена для ${aircraft.getName()}`);
        this.notify('runwayOccupied');
    }
    
    releaseAircraft(): void {
        if (this.assignedAircraft) {
            console.log(`Полоса ${this.id} освобождена от ${this.assignedAircraft.getName()}`);
            this.assignedAircraft = null;
            this.notify('runwayAvailable');
        }
    }
    
    getAssignedAircraft(): Aircraft | null {
        return this.assignedAircraft;
    }
}

// Использование
const atc = new AirTrafficControlMediator();

// Создаем полосы
const runway1 = new Runway('RWY-01');
const runway2 = new Runway('RWY-02');
atc.addRunway(runway1);
atc.addRunway(runway2);

// Создаем самолеты
const flight101 = new Aircraft('Flight 101');
const flight202 = new Aircraft('Flight 202');
const flight303 = new Aircraft('Flight 303');

atc.addAircraft(flight101);
atc.addAircraft(flight202);
atc.addAircraft(flight303);

console.log('=== Система управления воздушным движением ===');

// Симулируем запросы на посадку и взлет
flight101.requestLanding();
flight202.requestLanding();
flight303.requestTakeoff();

Когда использовать

Используйте Mediator, когда:

  • Много объектов взаимодействуют друг с другом
  • Объекты тесно связаны и сложно изменять их независимо
  • Нужно централизовать логику взаимодействия
  • Хотите уменьшить связанность между компонентами

Преимущества

  • Уменьшает связанность между компонентами
  • Централизует логику взаимодействия
  • Принцип единственной ответственности
  • Легко добавлять новые компоненты

Недостатки

  • Посредник может стать сложным и трудно поддерживаемым
  • Может стать "божественным объектом"
  • Увеличивает количество классов

Отличия от других паттернов

  • Observer — уведомляет множество объектов об изменениях
  • Facade — предоставляет упрощенный интерфейс к подсистеме
  • Command — инкапсулирует запросы как объекты

Реальный пример

// Mediator для системы чата
class ChatMediator {
    constructor() {
        this.users = new Map();
        this.rooms = new Map();
    }
    
    addUser(user) {
        this.users.set(user.getId(), user);
        user.setMediator(this);
    }
    
    addRoom(room) {
        this.rooms.set(room.getId(), room);
        room.setMediator(this);
    }
    
    notify(sender, event, data = {}) {
        if (sender instanceof User) {
            this.handleUserEvent(sender, event, data);
        } else if (sender instanceof Room) {
            this.handleRoomEvent(sender, event, data);
        }
    }
    
    handleUserEvent(user, event, data) {
        switch (event) {
            case 'sendMessage':
                this.handleMessage(user, data.message, data.roomId);
                break;
            case 'joinRoom':
                this.handleJoinRoom(user, data.roomId);
                break;
            case 'leaveRoom':
                this.handleLeaveRoom(user, data.roomId);
                break;
        }
    }
    
    handleRoomEvent(room, event, data) {
        switch (event) {
            case 'userJoined':
                this.broadcastToRoom(room.getId(), `${data.user.getName()} присоединился к комнате`);
                break;
            case 'userLeft':
                this.broadcastToRoom(room.getId(), `${data.user.getName()} покинул комнату`);
                break;
        }
    }
    
    handleMessage(user, message, roomId) {
        const room = this.rooms.get(roomId);
        if (room && room.hasUser(user)) {
            this.broadcastToRoom(roomId, `${user.getName()}: ${message}`);
        }
    }
    
    handleJoinRoom(user, roomId) {
        const room = this.rooms.get(roomId);
        if (room) {
            room.addUser(user);
            user.joinRoom(room);
        }
    }
    
    handleLeaveRoom(user, roomId) {
        const room = this.rooms.get(roomId);
        if (room) {
            room.removeUser(user);
            user.leaveRoom(room);
        }
    }
    
    broadcastToRoom(roomId, message) {
        const room = this.rooms.get(roomId);
        if (room) {
            room.getUsers().forEach(user => {
                user.receiveMessage(message);
            });
        }
    }
}

// Использование
const chatMediator = new ChatMediator();

const generalRoom = new Room('general');
const techRoom = new Room('tech');

chatMediator.addRoom(generalRoom);
chatMediator.addRoom(techRoom);

const alice = new User('Alice');
const bob = new User('Bob');
const charlie = new User('Charlie');

chatMediator.addUser(alice);
chatMediator.addUser(bob);
chatMediator.addUser(charlie);

// Симулируем взаимодействие
alice.joinRoom('general');
bob.joinRoom('general');
charlie.joinRoom('tech');

alice.sendMessage('Привет всем!', 'general');
bob.sendMessage('Привет, Alice!', 'general');
charlie.sendMessage('Обсуждаем новые технологии', 'tech');

Mediator — это централизованное управление взаимодействием!

Copyright © 2026