Какие паттерны проектирования вы знаете и в чём их особенности?

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Сложный
#Теория

Краткий ответ

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

  1. Порождающие (Creational): Отвечают за гибкое создание объектов.

    • Фабричный метод (Factory Method): Создание объектов через специальную функцию, а не напрямую.
    • Абстрактная фабрика (Abstract Factory): Создание семейств связанных объектов.
    • Одиночка (Singleton): Гарантирует, что у модуля будет только один экземпляр.
    • Строитель (Builder): Пошаговое создание сложных объектов.
  2. Структурные (Structural): Определяют, как объекты могут быть объединены для формирования более крупных структур.

    • Адаптер (Adapter): Позволяет объектам с несовместимыми интерфейсами работать вместе.
    • Декоратор (Decorator): Динамическое добавление новой функциональности объекту.
    • Фасад (Facade): Предоставляет простой интерфейс к сложной системе.
    • Прокси (Proxy): Подставляет вместо реального объекта другой, который контролирует доступ к нему.
  3. Поведенческие (Behavioral): Отвечают за эффективное взаимодействие между объектами.

    • Стратегия (Strategy): Позволяет определять семейство алгоритмов и делать их взаимозаменяемыми.
    • Наблюдатель (Observer): Определяет зависимость «один ко многим», где при изменении состояния одного объекта все зависимые оповещаются и обновляются.
    • Команда (Command): Превращает запрос в самостоятельный объект.
    • Итератор (Iterator): Даёт возможность последовательно обходить элементы составных объектов, не раскрывая их внутреннего представления.

Развернутый ответ с примерами

Порождающие паттерны

1. Одиночка (Singleton)

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

Особенность: Полезен для управления общим состоянием (например, конфигурация приложения, сервис логирования).

const AppConfig = (() => {
  let instance;
 
  function createInstance() {
    const config = { theme: 'dark' };
    return {
      getConfig: () => config,
    };
  }
 
  return {
    getInstance: () => {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    },
  };
})();
 
const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();
 
console.log(config1 === config2); // true

Структурные паттерны

2. Декоратор (Decorator)

Позволяет динамически добавлять объектам новую функциональность, оборачивая их в функции-декораторы.

Особенность: Гибкая альтернатива наследованию. Идеально подходит для расширения функциональности без изменения исходного объекта.

const createCoffee = () => ({
  cost: () => 5,
});
 
// Декоратор
const withMilk = (coffee) => ({
  ...coffee,
  cost: () => coffee.cost() + 2,
});
 
// Декоратор
const withSugar = (coffee) => ({
  ...coffee,
  cost: () => coffee.cost() + 1,
});
 
let myCoffee = createCoffee();
myCoffee = withMilk(myCoffee);
myCoffee = withSugar(myCoffee);
 
console.log(myCoffee.cost()); // 8 (5 + 2 + 1)

3. Фасад (Facade)

Предоставляет унифицированную и упрощенную функцию-фасад для взаимодействия со сложной подсистемой.

Особенность: Скрывает сложность. Например, можно создать фасад для работы с браузерными API (DOM, Fetch, LocalStorage).

// Сложная подсистема
const domApi = {
  createElement: (tag) => { /* ... */ },
  addStyle: (element, styles) => { /* ... */ },
};
 
const fetchApi = {
  get: (url) => { /* ... */ },
};
 
// Фасад
const createAppFacade = () => ({
  fetchAndDisplay: (url, elementId) => {
    const data = fetchApi.get(url);
    const element = domApi.createElement('div');
    // ... логика отображения
  }
});
 
const app = createAppFacade();
// app.fetchAndDisplay('/api/data', '#root');

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

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

Создает механизм подписки, позволяющий одним объектам (наблюдателям) следить за изменениями в другом объекте (субъекте).

Особенность: Основа реактивного программирования. Используется в управлении состоянием (Redux, MobX) и событийной модели в браузере.

const createNewsPublisher = () => {
  let subscribers = [];
 
  return {
    subscribe: (observer) => {
      subscribers.push(observer);
    },
    unsubscribe: (observer) => {
      subscribers = subscribers.filter(obs => obs !== observer);
    },
    notify: (news) => {
      subscribers.forEach(observer => observer.update(news));
    },
  };
};
 
const createReader = (name) => ({
  update: (news) => {
    console.log(`${name} прочитал новость: ${news}`);
  },
});
 
const publisher = createNewsPublisher();
const reader1 = createReader('Иван');
const reader2 = createReader('Мария');
 
publisher.subscribe(reader1);
publisher.subscribe(reader2);
 
publisher.notify('Вышла новая версия JavaScript!');
// Иван прочитал новость: Вышла новая версия JavaScript!
// Мария прочитал новость: Вышла новая версия JavaScript!

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

Определяет семейство схожих алгоритмов (функций) и позволяет передавать их в качестве аргумента, делая их взаимозаменяемыми.

Особенность: Позволяет менять логику «на лету». Например, выбор способа сортировки или метода валидации формы.

const validate = (strategy, value) => {
  return strategy(value);
};
 
const isRequired = (value) => !!value;
const isEmail = (value) => /@/.test(value);
 
console.log(validate(isRequired, '')); // false
console.log(validate(isEmail, 'test@test.com')); // true