JavaRush /Курсы /Design Patterns, SQL, и Docker /Поведенческие паттерны

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

Design Patterns, SQL, и Docker
2 уровень , 0 лекция
Открыта

Iterator

Итератор — паттерн, который дает последовательный доступ к элементам коллекции, не раскрывая её внутреннего устройства (будь то массив, связный список или дерево).

Обход коллекции

    graph LR
        Collection["Collection (Array/Set/Map)"] -- "getIterator()" --> Iterator
        Iterator -- ".next()" --> Item1[Item 1]
        Iterator -- ".next()" --> Item2[Item 2]
        Iterator -- ".next()" --> Done[Done: true]
    

В Java или C++ вы часто создаете итераторы вручную. В JavaScript этот паттерн встроен в сам язык на уровне ядра. Всякий раз, когда вы пишете цикл for..of или используете спред-оператор ..., вы неявно используете Итератор.

Генераторы и Протоколы

В JS есть специальный символ Symbol.iterator. Если у объекта есть этот метод, он становится "перебираемым". Самый мощный инструмент здесь — Генераторы (функции со звездочкой function*).


// Функция-генератор (Iterator Pattern "из коробки")
function* idGenerator() {
    let id = 1;
    while (true) {
        yield id++; // Возвращает значение и "замораживает" выполнение
    }
}

const iterator = idGenerator();

console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

Архитектурная справка

Раньше в вебе правил паттерн MVC. Контроллер менял Модель, Модель обновляла Вид, Вид мог дернуть Контроллер. В больших приложениях стрелки запутывались, и никто не понимал, почему данные изменились.

Facebook (Meta) предложил решение, основанное на паттернах Command и Observer, назвав его Flux (а позже — Redux). Главное правило: Данные текут только в одну сторону.

    graph LR
        Action["Action (Command)"] --> Dispatcher
        Dispatcher --> Store["Store (Model)"]
        Store -- "Notify (Observer)" --> View[React View]
        View -- "User Click" --> Action
    

Command

Команда — превращает запрос (действие) в объект. Этот объект содержит всё необходимое для выполнения действия: имя метода, параметры и данные.

Инкапсуляция действия

    flowchart LR
        User((User)) -- "Clicks Button" --> UI[UI Component]
        UI -- "Creates" --> Action["Command Object\n{ type: 'ADD_TODO', text: 'Buy Milk' }"]
        Action -- "Dispatched to" --> Store[Store / System]
        Store -- "Executes" --> Update[Update State]
    

В классическом ООП выделяют 4 роли: Command, Receiver, Invoker, Client. Звучит сложно? Давайте посмотрим на это глазами Frontend-разработчика.

Redux Actions

Вы учили Redux и вы уже знаете этот паттерн. Redux Action — это и есть Команда.

Вместо того чтобы напрямую менять стейт state.count++ (императивный подход), мы создаем объект-команду { type: 'INCREMENT' } и отправляем её диспетчеру. Это позволяет:

  • Логировать все действия Redux DevTools.
  • Делать отмену действий, просто удаляя последнюю команду из истории.
  • Передавать команды по сети.

// 1. Command (Команда)
const addToCart = (product) => ({
    type: 'ADD_TO_CART',
    payload: product
});

// 2. Invoker (Тот, кто вызывает)
const dispatch = (action) => {
    console.log('Executing command:', action.type);
    // logic...
};

// 3. Client (UI)
button.onClick = () => dispatch(addToCart({ id: 1, name: 'Phone' }));

Observer

Наблюдатель — механизм подписки. Один объект (Субъект) меняет состояние, и все, кто на него подписался (Наблюдатели), автоматически получают уведомление.

One-to-Many Dependency

    sequenceDiagram
        participant Subject as YouTube Channel
        participant Obs1 as Subscriber 1
        participant Obs2 as Subscriber 2

        Note over Subject: New Video Uploaded!
        Subject->>Obs1: Notify("New Video!")
        Subject->>Obs2: Notify("New Video!")
        Obs1->>Obs1: Watch Video
        Obs2->>Obs2: Watch Video
    

Весь браузер построен на этом паттерне. Метод addEventListener — это чистейшая реализация Наблюдателя. Вы подписываетесь на клики, движения мыши или загрузку страницы.

RxJS и React Effects

  • DOM Events: button.addEventListener('click', handler).
  • React useEffect: вы "наблюдаете" за переменными в массиве зависимостей. [userId] изменился -> эффект сработал.
  • RxJS: библиотека, которая возводит этот паттерн в абсолют. Всё есть поток данных, на который можно подписаться.

// Простейший Observable на JS
class NewsChannel {
    constructor() {
        this.subscribers = [];
    }

    subscribe(callback) {
        this.subscribers.push(callback);
    }

    broadcast(news) {
        this.subscribers.forEach(sub => sub(news));
    }
}

const channel = new NewsChannel();
channel.subscribe(news => console.log('Subscriber 1 read:', news));
channel.broadcast('Breaking News: JS is awesome!');

Visitor

Посетитель — позволяет добавить новую операцию для целой структуры объектов, не меняя классы этих объектов. Это "гость", который ходит по домам и делает работу, которую хозяева делать не умеют.

Обход дерева структур

    graph TD
        Visitor((Visitor))
        Root[Root Node]
        ChildA[Node A]
        ChildB[Node B]
        
        Root --> ChildA
        Root --> ChildB
        
        Visitor -.->|Visits| Root
        Visitor -.->|Visits| ChildA
        Visitor -.->|Visits| ChildB
    

В повседневной UI-разработке встречается редко. Но это ключевой паттерн для инструментов разработчика (Babel, ESLint, Prettier).

Babel и AST

Ваш код — это дерево (AST - Abstract Syntax Tree). Babel использует паттерн Visitor, чтобы пройти по этому дереву и превратить современный JS в старый (транспиляция).

Пример: посетитель находит все узлы "VariableDeclaration" с типом "const" и меняет их на "var". Сам узел кода не знает, как себя транспилировать, это делает внешний Visitor.

Mediator

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

Централизация хаоса

    graph TD
        subgraph Chaos ["Без Посредника"]
            A1((A)) <--> B1((B))
            A1 <--> C1((C))
            B1 <--> C1
        end

        subgraph Order ["С Посредником"]
            A2((A)) <--> M[Mediator]
            B2((B)) <--> M
            C2((C)) <--> M
        end
    

Главное отличие от Observer: В Observer "Субъект" просто рассылает уведомления и ему всё равно, кто слушает. В Mediator центральный объект содержит бизнес-логику принятия решений.

Сложные формы и чаты

Представьте форму регистрации с полями: "Страна", "Город", "Индекс".

  • Выбор "Страны" должен очистить "Город".
  • Выбор "Города" должен подсказать "Индекс".

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


// Mediator (Координатор чата)
class ChatRoom {
    showMessage(user, message) {
        const time = new Date().toLocaleTimeString();
        const sender = user.getName();
        console.log(`${time} [${sender}]: ${message}`);
        // Здесь могла быть логика фильтрации мата или отправки на сервер
    }
}

class User {
    constructor(name, chatMediator) {
        this.name = name;
        this.chat = chatMediator;
    }
    getName() { return this.name; }
    send(message) {
        // User не знает, как отправить сообщение, он просит посредника
        this.chat.showMessage(this, message);
    }
}
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ