Паттерны проектирования — это как рецепты в кулинарной книге. Вы не обязаны следовать им дословно, но они экономят время и спасают от типичных ошибок. В этой статье разберём, какие паттерны стоит использовать в JavaScript-проектах, а какие — обходить стороной.

Digital-студия WNDER

Что такое паттерн проектирования и зачем он нужен

Паттерн — это проверенное решение типовой задачи. Вы не изобретаете велосипед, а берёте готовую конструкцию, которую уже обкатали тысячи разработчиков. В JavaScript паттерны особенно полезны, потому что язык гибкий: можно писать и процедурно, и объектно, и функционально. Без структуры легко превратить проект в спагетти.

Основные плюсы:

  • Код становится понятнее для других (и для вас через полгода).
  • Меньше дублирования и больше переиспользования.
  • Проще тестировать и расширять.

Но есть и минус: не тащите паттерны туда, где они не нужны. Иногда простая функция лучше навороченного фабричного метода.

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

Популярные паттерны в JavaScript: от простого к сложному

Начнём с самых ходовых. Их вы встретите в любом крупном проекте.

Модуль (Module)

Самый распространённый паттерн в JS. Он позволяет инкапсулировать код и не засорять глобальную область. Раньше использовали IIFE, сейчас — ES-модули.

// ES-модуль: файл math.js
export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// Использование в другом файле
import { add, multiply } from './math.js';
console.log(add(2, 3)); // 5

Просто, эффективно, поддерживается всеми современными сборщиками.

Фабрика (Factory)

Когда нужно создавать объекты, но вы не хотите светить классы и логику создания — используйте фабрику. Она возвращает готовый объект, скрывая детали.

function createUser(type) {
  const roles = {
    admin: { canEdit: true, canDelete: true },
    user: { canEdit: false, canDelete: false }
  };

  return {
    type,
    ...roles[type],
    greet() {
      console.log(`Привет, я ${this.type}`);
    }
  };
}

const admin = createUser('admin');
admin.greet(); // Привет, я admin
console.log(admin.canEdit); // true

Фабрика хороша, когда создание объекта — это не просто вызов конструктора, а целый процесс.

Наблюдатель (Observer)

Классика для работы с событиями. Один объект (издатель) уведомляет других (подписчиков) об изменениях. В JS это встроено в DOM-события, но можно реализовать и вручную.

class EventEmitter {
  constructor() {
    this.listeners = {};
  }

  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }

  emit(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(cb => cb(data));
    }
  }
}

const emitter = new EventEmitter();
emitter.on('message', (text) => console.log(`Получено: ${text}`));
emitter.emit('message', 'Привет, мир!'); // Получено: Привет, мир!

Такой паттерн часто используют в React-компонентах или в Node.js для работы с потоками.

Стратегия (Strategy)

Позволяет менять алгоритм на лету. Вместо кучи if-else вы выносите каждую логику в отдельную функцию и выбираете нужную.

const strategies = {
  card: (amount) => `Оплата картой на ${amount} руб.`,
  cash: (amount) => `Оплата наличными: ${amount} руб.`,
  crypto: (amount) => `Оплата криптой: ${amount} руб.`
};

function pay(method, amount) {
  if (!strategies[method]) {
    throw new Error('Неизвестный метод оплаты');
  }
  return strategies[method](amount);
}

console.log(pay('card', 1000)); // Оплата картой на 1000 руб.
console.log(pay('crypto', 500)); // Оплата криптой: 500 руб.

Код становится чище, а добавление нового способа оплаты занимает одну строчку.

Когда паттерны вредны: антипаттерны

Некоторые паттерны в JavaScript устарели или изначально были плохими. Например, Singleton (Одиночка) часто критикуют за глобальное состояние и сложности с тестированием. В JS его можно заменить на модуль с единственным экземпляром, но лучше избегать, если нет реальной необходимости.

Ещё один пример — глубокая вложенность (callback hell). Это не совсем паттерн, но антипример. Решается промисами или async/await.

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

Сравнение паттернов: что и когда использовать

Чтобы не запутаться, вот таблица с краткими рекомендациями.

Паттерн Когда использовать Пример из жизни
Модуль Всегда, для организации кода Любой файл с экспортами
Фабрика Создание объектов со сложной логикой Генерация пользователей по ролям
Наблюдатель Событийная модель, реактивность Клик по кнопке, обновление UI
Стратегия Выбор алгоритма во время выполнения Способы оплаты, сортировки

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

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

  • Начинайте с малого: добавьте один паттерн там, где код уже начал путаться.
  • Обсуждайте с командой: паттерн должен быть понятен всем, иначе он станет обузой.
  • Изучайте исходники библиотек: в React, Vue, Lodash паттерны используются повсеместно.
  • Не бойтесь рефакторить: если паттерн не прижился — перепишите.
Правило: паттерн — это инструмент, а не религия. Используйте там, где он реально упрощает жизнь.

Что в итоге

Паттерны проектирования в JavaScript — это не догма, а набор проверенных приёмов. Они помогают писать чистый, расширяемый код, но только если применять их с умом. Начните с модулей и фабрик, добавьте наблюдателя для событий, а стратегию — для гибких алгоритмов. И помните: главное — чтобы код был понятен вам и вашей команде.

Экспериментируйте, ошибайтесь, рефакторьте. Со временем вы начнёте чувствовать, где какой паттерн уместен.

Студия WNDER