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

Digital-студия WNDER

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

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

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

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

Singleton: один на всех

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

Пример на JavaScript:

class Database {
  constructor() {
    if (Database.instance) {
      return Database.instance;
    }
    this.connection = 'connected';
    Database.instance = this;
  }

  query(sql) {
    console.log(`Выполняю запрос: ${sql}`);
  }
}

const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true

Как видите, сколько бы раз вы ни вызывали new Database(), вы всегда получаете один и тот же объект. Удобно, когда нужно разделять состояние между разными частями приложения.

Но будьте осторожны: синглтон создаёт глобальное состояние, а это может усложнить тестирование. Используйте его только там, где действительно нужен один экземпляр.

Factory: создаём объекты, не зная деталей

Фабрика — это паттерн, который делегирует создание объектов подклассам или отдельным методам. Вы просто говорите: «Дай мне транспортное средство», а фабрика решает, что именно вернуть — машину, мотоцикл или велосипед.

Пример:

class Car {
  drive() { console.log('Еду на машине'); }
}

class Bike {
  drive() { console.log('Еду на велосипеде'); }
}

class VehicleFactory {
  createVehicle(type) {
    switch(type) {
      case 'car': return new Car();
      case 'bike': return new Bike();
      default: throw new Error('Неизвестный тип');
    }
  }
}

const factory = new VehicleFactory();
const myCar = factory.createVehicle('car');
myCar.drive(); // Еду на машине

Фабрика полезна, когда логика создания объекта сложная или зависит от условий. Она изолирует код создания от остальной программы.

Observer: подписываемся на события

Наблюдатель — один из самых востребованных паттернов в JavaScript. Он позволяет объектам подписываться на события и получать уведомления при их возникновении. Это основа событийной модели в браузере и многих библиотек.

Пример простого наблюдателя:

class Subject {
  constructor() {
    this.observers = [];
  }

  subscribe(observer) {
    this.observers.push(observer);
  }

  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log(`Получено: ${data}`);
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Привет, мир!');

Наблюдатель отлично подходит для реализации подписок, обработки пользовательских действий и связи между компонентами.

Module: прячем внутренности

Модуль — это способ инкапсулировать код, скрывая детали реализации и предоставляя только публичный интерфейс. В JavaScript модули можно создавать с помощью замыканий или ES6-модулей.

Пример с замыканием:

const counterModule = (function() {
  let count = 0;

  function increment() {
    count++;
  }

  function getCount() {
    return count;
  }

  return {
    increment,
    getCount
  };
})();

counterModule.increment();
console.log(counterModule.getCount()); // 1

Переменная count недоступна снаружи, но мы можем управлять ею через методы. Это защищает данные от случайного изменения.

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

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

Паттерн Когда использовать Пример
Singleton Нужен единственный экземпляр (конфиг, логгер) Подключение к БД
Factory Создание объектов с разной логикой Фабрика UI-компонентов
Observer Реакция на события, подписки Обработка кликов, стримы
Module Инкапсуляция, скрытие деталей Библиотеки, утилиты

Не бойтесь комбинировать паттерны. Например, фабрика может использовать синглтон для создания единственного экземпляра какого-то сервиса.

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

Заключение

Паттерны проектирования — это не серебряная пуля, но мощный инструмент в руках разработчика. Они помогают писать код, который легко читать, тестировать и расширять. В JavaScript особенно полезны Singleton, Factory, Observer и Module. Начните с одного паттерна, примените его в реальном проекте, и вы почувствуете разницу. Главное — не усложняйте там, где можно обойтись простым решением.

Помните: паттерны — это язык общения между разработчиками. Зная их, вы быстрее поймёте чужой код и сможете объяснить свой.

Студия WNDER