> Какие паттерны проектирования вы знаете и использовали в работе (JavaScript)

Уровень: senior · Роль: frontend · Язык: JavaScript · Категория: Технические вопросы

Компании: Mosline

Стек: Node.js, JavaScript

> Пример ответа

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

В работе я активно использовал порождающие, структурные и поведенческие паттерны. Из порождающих - Singleton для глобального состояния и Factory для создания сложных объектов. Из структурных - Module, Proxy (для кэширования API-запросов) и Decorator (для middleware в Express). Из поведенческих - Observer (EventEmitter в Node.js), Strategy (для переключения алгоритмов валидации) и Command (для отмены действий в редакторе). Также применял паттерн Container/DI для управления зависимостями.

Подробное объяснение

Паттерны проектирования - это проверенные решения типовых задач, которые возникают при разработке ПО. В контексте JavaScript и Node.js они особенно важны из-за асинхронной природы языка и особенностей прототипного наследования.

Порождающие паттерны управляют процессом создания объектов:

  • Singleton - гарантирует единственный экземпляр класса. В Node.js модули кэшируются, поэтому каждый модуль по умолчанию является синглтоном.
  • Factory - инкапсулирует логику создания объектов, что полезно при сложной инициализации или условном выборе реализации.
  • Builder - удобен для конструирования объектов с множеством опциональных параметров.

Структурные паттерны определяют отношения между объектами:

  • Module - основа организации кода в JS, инкапсулирует приватные данные через замыкания.
  • Proxy - контролирует доступ к объекту, часто используется для кэширования, логирования или ленивой инициализации.
  • Decorator - динамически добавляет поведение объекту. В Node.js это основа middleware-пайплайнов.
  • Adapter - преобразует интерфейс одного класса в интерфейс, ожидаемый клиентом.

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

  • Observer - реализует механизм подписки и уведомления. EventEmitter в Node.js - классическая реализация.
  • Strategy - позволяет менять алгоритм на лету, выделяя его в отдельный класс.
  • Command - инкапсулирует запрос как объект, что даёт возможность откладывать выполнение, логировать или отменять действия.
  • Middleware - цепочка обработчиков, где каждый может прервать или модифицировать запрос.

На практике

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

Singleton применял для конфигурации приложения и подключения к базе данных. В Node.js это тривиально: module.exports = new Database().

Factory использовал при создании различных типов парсеров для импорта данных - в зависимости от формата файла (CSV, JSON, XML) фабрика возвращала нужный парсер.

Proxy реализовал для кэширования ответов внешнего API. Прокси-объект проверял, есть ли данные в кэше, и только при промахе обращался к реальному API.

Observer - основа event-driven архитектуры в Node.js. Использовал EventEmitter для системы уведомлений: когда пользователь совершал действие, эмиттер оповещал подписанные сервисы (логирование, отправка email, обновление кэша).

Strategy применял для валидации форм на фронтенде. Каждое правило валидации (required, email, minLength) - отдельная стратегия, которая комбинировалась в цепочку.

Command - в редакторе текста для реализации undo/redo. Каждое действие (вставка, удаление) оборачивалось в команду с методами execute и undo.

Middleware - основа Express и Koa. Каждый middleware - это декоратор, который может модифицировать request/response или прервать цепочку.

Пример кода

JAVASCRIPT
// Пример паттерна Strategy для валидации
class ValidationStrategy {
validate(value) { throw new Error('Not implemented'); }
}
class RequiredStrategy extends ValidationStrategy {
validate(value) {
return value !== undefined && value !== null && value !== '';
}
}
class EmailStrategy extends ValidationStrategy {
validate(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
}
class MinLengthStrategy extends ValidationStrategy {
constructor(min) {
super();
this.min = min;
}
validate(value) {
return typeof value === 'string' && value.length >= this.min;
}
}
class Validator {
constructor() {
this.strategies = [];
}
addStrategy(strategy) {
this.strategies.push(strategy);
}
validate(value) {
return this.strategies.every(s => s.validate(value));
}
}
// Использование
const validator = new Validator();
validator.addStrategy(new RequiredStrategy());
validator.addStrategy(new MinLengthStrategy(3));
console.log(validator.validate('ab')); // false
console.log(validator.validate('abc')); // true

Как отвечать на собеседовании

Начните с краткого перечисления основных групп паттернов и приведите 1-2 примера из каждой. Затем выберите один паттерн и подробно объясните, какую проблему он решал в вашем проекте. Важно не просто назвать паттерны, а показать понимание trade-off: когда паттерн полезен, а когда избыточен.

Хорошо упомянуть, что в JavaScript некоторые паттерны реализуются нативно (Module через модули ES, Singleton через кэширование модулей Node.js, Observer через EventEmitter). Это показывает глубокое понимание языка.

Избегайте перечисления всех паттернов подряд - это выглядит как заученный список. Лучше 3-4 паттерна с конкретными примерами из практики.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание не просто названий, а сути паттернов - какую проблему они решают и какие компромиссы несут.
  • Умение применять паттерны в контексте JavaScript/Node.js, а не абстрактно из книг GoF.
  • Способность выбирать правильный паттерн под задачу, а не использовать все подряд.
  • Понимание, что паттерны - это инструменты, а не догмы. Иногда проще написать функцию, чем реализовывать полноценный паттерн.

Типичные ошибки

  • Перечисление всех паттернов из книги GoF без привязки к реальному опыту.
  • Попытка втиснуть паттерн туда, где он не нужен (overengineering).
  • Путаница между паттернами и архитектурными стилями (например, MVC - это архитектурный паттерн, а не паттерн проектирования).
  • Непонимание, что в JavaScript многие паттерны реализуются проще из-за first-class functions и динамической типизации.
  • Игнорирование нативных реализаций: например, предлагать реализовать Observer вручную, когда EventEmitter уже есть в Node.js.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью