Паттерны проектирования в JavaScript: продвинутые техники с примерами

Онлайн-тренажер Python для начинающих

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

Начать курс

Паттерны проектирования в JavaScript: продвинутый уровень с примерами на Node.js

Паттерны проектирования — это проверенные архитектурные решения для типовых задач. Если базовые паттерны (Одиночка, Фабрика, Наблюдатель) уже знакомы, то настало время углубиться в более сложные конструкции. В этой статье мы разберём четыре продвинутых паттерна GoF на примерах JavaScript (Node.js): Компоновщик, Цепочка обязанностей, Состояние и Хранитель. Эти паттерны помогут вам писать гибкий, масштабируемый и поддерживаемый код.

1. Компоновщик (Composite)

Назначение: Позволяет сгруппировать объекты в древовидную структуру и работать с каждым объектом или группой единообразно.

Пример: Система файлов, где папка может содержать файлы и другие папки. Мы хотим подсчитать общий размер.

// Компонент
class FileSystemComponent {
  getSize() {
    throw new Error('Метод getSize() должен быть реализован');
  }
}

// Лист
class File extends FileSystemComponent {
  constructor(name, size) {
    super();
    this.name = name;
    this.size = size;
  }

  getSize() {
    return this.size;
  }
}

// Контейнер (узел)
class Folder extends FileSystemComponent {
  constructor(name) {
    super();
    this.name = name;
    this.children = [];
  }

  add(component) {
    this.children.push(component);
  }

  getSize() {
    return this.children.reduce((total, child) => total + child.getSize(), 0);
  }
}

// Использование
const root = new Folder('root');
const src = new Folder('src');
const index = new File('index.js', 500);
const utils = new File('utils.js', 300);

src.add(index);
src.add(utils);
root.add(src);
root.add(new File('package.json', 200));

console.log(`Общий размер: ${root.getSize()} байт`); // 1000 байт

Плюсы: Упрощает работу с деревьями, скрывает сложность иерархии от клиента.

2. Цепочка обязанностей (Chain of Responsibility)

Назначение: Позволяет передавать запрос по цепочке обработчиков, пока один из них не обработает запрос.

Пример: Обработка HTTP-запросов в Express.js (middleware).

class Handler {
  setNext(handler) {
    this.next = handler;
    return handler;
  }

  handle(request) {
    if (this.next) {
      return this.next.handle(request);
    }
    return null;
  }
}

class AuthHandler extends Handler {
  handle(request) {
    if (!request.token) {
      return 'Ошибка: неавторизован';
    }
    console.log('AuthHandler: проверка пройдена');
    return super.handle(request);
  }
}

class RoleHandler extends Handler {
  handle(request) {
    if (request.role !== 'admin') {
      return 'Ошибка: недостаточно прав';
    }
    console.log('RoleHandler: роль подтверждена');
    return super.handle(request);
  }
}

class LoggingHandler extends Handler {
  handle(request) {
    console.log(`LoggingHandler: запрос от ${request.user}`);
    return super.handle(request);
  }
}

// Сборка цепочки
const auth = new AuthHandler();
const role = new RoleHandler();
const log = new LoggingHandler();
auth.setNext(role).setNext(log);

const request = { token: 'abc123', role: 'admin', user: 'Ivan' };
const result = auth.handle(request);
console.log(result); // null (все ок)

const badRequest = { token: 'abc123', role: 'user', user: 'Petr' };
console.log(auth.handle(badRequest)); // 'Ошибка: недостаточно прав'

Плюсы: Снижает связанность, легко добавлять/удалять обработчики.

3. Состояние (State)

Назначение: Позволяет объекту менять поведение в зависимости от своего внутреннего состояния.

Пример: Документ с состояниями: Черновик, На модерации, Опубликован.

class Document {
  constructor() {
    this.state = new DraftState(this);
  }

  setState(state) {
    this.state = state;
  }

  publish() {
    this.state.publish();
  }

  delete() {
    this.state.delete();
  }
}

// Абстрактное состояние
class State {
  constructor(document) {
    this.document = document;
  }

  publish() {
    throw new Error('Метод publish() не реализован');
  }

  delete() {
    throw new Error('Метод delete() не реализован');
  }
}

class DraftState extends State {
  publish() {
    console.log('Черновик отправлен на модерацию');
    this.document.setState(new ModerationState(this.document));
  }

  delete() {
    console.log('Черновик удалён');
  }
}

class ModerationState extends State {
  publish() {
    console.log('Документ опубликован');
    this.document.setState(new PublishedState(this.document));
  }

  delete() {
    console.log('Документ отклонён модератором');
    this.document.setState(new DraftState(this.document));
  }
}

class PublishedState extends State {
  publish() {
    console.log('Документ уже опубликован');
  }

  delete() {
    console.log('Публикация отменена');
    this.document.setState(new DraftState(this.document));
  }
}

// Использование
const doc = new Document();
doc.publish(); // Черновик отправлен на модерацию
doc.publish(); // Документ опубликован
doc.publish(); // Документ уже опубликован
doc.delete();  // Публикация отменена

Плюсы: Избавляет от больших условных операторов, упрощает добавление новых состояний.

4. Хранитель (Memento)

Назначение: Позволяет сохранять и восстанавливать предыдущее состояние объекта без раскрытия его внутренней структуры.

Пример: Редактор текста с функцией отмены (undo).

// Хранитель (Memento)
class TextMemento {
  constructor(content) {
    this.content = content;
  }

  getContent() {
    return this.content;
  }
}

// Создатель (Originator)
class TextEditor {
  constructor() {
    this.content = '';
  }

  type(text) {
    this.content += text;
  }

  save() {
    return new TextMemento(this.content);
  }

  restore(memento) {
    this.content = memento.getContent();
  }

  show() {
    console.log(`Текущий текст: "${this.content}"`);
  }
}

// Опекун (Caretaker)
class History {
  constructor() {
    this.mementos = [];
  }

  push(memento) {
    this.mementos.push(memento);
  }

  pop() {
    return this.mementos.pop();
  }
}

// Использование
const editor = new TextEditor();
const history = new History();

editor.type('Привет, ');                

Блоги

Рекомендаций по книгам