Вы когда-нибудь писали код, а потом через месяц смотрели на него и думали: «Что за каша?». Или переделывали одну и ту же логику в разных местах? Знакомо. Паттерны проектирования — это как готовые рецепты для типовых задач. Они помогают структурировать код, делать его понятным и гибким. Сегодня разберем три самых популярных паттерна в JavaScript: Singleton, Factory и Observer. Без заумных слов, только практика.

Паттерн Singleton: когда объект должен быть один

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

const Database = (function() {
  let instance;

  function createInstance() {
    return {
      connect: function() {
        console.log('Подключение к базе данных');
      },
      query: function(sql) {
        console.log('Выполнение запроса: ' + sql);
      }
    };
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

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

Лайфхак: В современных проектах для Singleton используйте ES6 модули — они сами создают один экземпляр при импорте.

Правило: Singleton полезен для управления состоянием приложения (например, хранилище Redux) или для работы с API, где нужно избегать дублирования запросов.

Паттерн Factory: фабрика объектов без лишнего кода

Допустим, вы создаете игру. Есть разные типы врагов: орки, эльфы, драконы. Каждый тип имеет свои характеристики. Если писать отдельный класс для каждого, код раздуется. Factory позволяет создавать объекты на основе переданного типа, скрывая логику создания. В JavaScript это часто реализуется через функцию, которая возвращает объект.

function EnemyFactory(type, level) {
  const enemies = {
    orc: { health: 100, damage: 20, color: 'green' },
    elf: { health: 70, damage: 30, color: 'blue' },
    dragon: { health: 300, damage: 50, color: 'red' }
  };

  const base = enemies[type] || enemies.orc;
  return {
    type: type,
    health: base.health * level,
    damage: base.damage * level,
    color: base.color,
    attack: function() {
      console.log(this.type + ' атакует с уроном ' + this.damage);
    }
  };
}

const orc = EnemyFactory('orc', 2);
const dragon = EnemyFactory('dragon', 1);
orc.attack(); // orc атакует с уроном 40

Лайфхак: Factory хорошо комбинируется с паттерном Strategy, когда нужно менять поведение объектов на лету.

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

Паттерн Observer: подписка на события

Представьте, что у вас есть онлайн-магазин. Когда товар заканчивается, нужно уведомить всех, кто его ждет. 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 {
  constructor(name) {
    this.name = name;
  }

  update(data) {
    console.log(this.name + ' получил уведомление: ' + data);
  }
}

const subject = new Subject();
const user1 = new Observer('Иван');
const user2 = new Observer('Мария');

subject.subscribe(user1);
subject.subscribe(user2);
subject.notify('Товар появился в наличии!');
// Иван получил уведомление: Товар появился в наличии!
// Мария получил уведомление: Товар появился в наличии!

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

Правило: Observer отлично подходит для систем уведомлений, чатов, обновления UI при изменении данных.

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

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

Что в итоге

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

Совет: Если вы пишете на PHP, эти же паттерны работают аналогично. Разница только в синтаксисе. Например, Singleton в PHP через статическое свойство и приватный конструктор.

Студия WNDER