Back

Гибкое создание объектов с помощью паттерна Builder в JavaScript

Гибкое создание объектов с помощью паттерна Builder в JavaScript

У вас есть функция, которая создает объекты пользователей. Сначала она принимает три параметра, затем их становится пять, потом семь. Половина из них необязательные. Вызывающий код должен помнить точный порядок, и одна ошибка в позиции незаметно создает неработающий объект. Именно эту проблему решает паттерн Builder в JavaScript.

Ключевые выводы

  • Паттерн Builder конструирует объекты пошагово с помощью цепочки setter-методов и финального вызова build(), заменяя подверженные ошибкам списки позиционных аргументов.
  • Fluent API, где каждый setter возвращает this, делает код самодокументируемым и независимым от порядка вызовов.
  • Используйте этот паттерн для объектов с множеством необязательных параметров, проверкой обязательных полей или правилами валидации. В более простых случаях применяйте объектные литералы или фабричные функции.
  • Метод build() — это единственное место для валидации обязательных полей и возврата чистого, замороженного объекта, отдельного от самого builder’а.

Что такое паттерн Builder в JavaScript?

Паттерн Builder — это порождающий паттерн проектирования, который конструирует объекты пошагово, а не все сразу. Вместо передачи всех значений в один вызов конструктора вы выстраиваете цепочку setter-методов и завершаете создание шагом build(), который валидирует и возвращает готовый объект.

Это не универсальное решение. Для простых объектов с двумя-тремя четко определенными полями обычный объектный литерал или фабричная функция будут чище. Паттерн Builder оправдывает себя, когда создание объекта включает:

  • Множество необязательных параметров, где порядок не имеет значения
  • Правила валидации, которые должны выполняться до использования объекта
  • Обязательные поля, которые необходимо проверить при создании
  • Многошаговое конструирование, где промежуточные состояния не должны быть доступны

Проблема: загрязнение конструктора

Рассмотрим типичный пример:

// ❌ Трудно читать, легко перепутать порядок аргументов
const request = new ApiRequest('GET', '/users', null, true, 5000, 'json')

Шесть позиционных аргументов. Без меток. Без валидации. Если вы перепутаете два значения, ничто вас не предупредит.

Чистый пример паттерна Builder в JavaScript

Вот реализация на основе классов с использованием fluent API в JavaScript — где каждый setter возвращает this, позволяя выстраивать цепочку методов:

class ApiRequestBuilder {
  constructor() {
    this.method = 'GET'        // разумное значение по умолчанию
    this.url = null
    this.body = null
    this.timeout = 3000        // таймаут по умолчанию
    this.responseType = 'json'
  }

  setMethod(method) {
    this.method = method
    return this
  }

  setUrl(url) {
    this.url = url
    return this
  }

  setBody(body) {
    this.body = body
    return this
  }

  setTimeout(ms) {
    this.timeout = ms
    return this
  }

  build() {
    if (!this.url) {
      throw new Error('URL is required')
    }
    // Возвращаем простой, замороженный объект — не сам builder
    return Object.freeze({
      method: this.method,
      url: this.url,
      body: this.body,
      timeout: this.timeout,
      responseType: this.responseType,
    })
  }
}

// Использование
const request = new ApiRequestBuilder()
  .setUrl('/api/users')
  .setMethod('POST')
  .setBody({ name: 'Alice' })
  .build()

Каждый вызов самодокументируется. Валидация выполняется в build() перед использованием объекта. Значения по умолчанию применяются автоматически. Обратите внимание, что build() возвращает замороженный простой объект — не builder — что сохраняет результат чистым и предотвращает случайные изменения.

Builder против более простых альтернатив

СценарийЛучший подход
2–3 обязательных поля, без валидацииОбъектный литерал или фабричная функция
Необязательные поля, без правилИменованные параметры через createUser({ name, age })
Обязательные поля + валидация + значения по умолчаниюПаттерн Builder
Сложное многошаговое конструированиеПаттерн Builder

Фабричная функция с именованными параметрами вроде createRequest({ url, method = 'GET' }) хорошо справляется со многими случаями. Используйте builder, когда логика валидации или последовательность действий делают такую функцию трудной для понимания.

Заметка о TypeScript

TypeScript может сделать builder’ы значительно безопаснее. Вы можете обеспечить, чтобы build() вызывался только после того, как были вызваны обязательные setter’ы, используя условные типы или интерфейс step-builder. Если ваш проект использует TypeScript, это стоит изучить — но базовый JavaScript-паттерн работает хорошо и без этого.

Заключение

Используйте паттерн Builder, когда создание объекта требует соблюдения правил, а не просто как стратегию создания объектов по умолчанию. Fluent API делает код читаемым, шаг build() делает валидацию явной, а значения по умолчанию уменьшают шум. Для всего более простого фабричная функция или простой объектный литерал — правильный инструмент.

Часто задаваемые вопросы

Объект опций группирует именованные параметры, что решает проблему позиционных аргументов. Builder добавляет шаг build, где вы можете валидировать обязательные поля, применять ограничения и замораживать результат перед его использованием. Если вам нужны эти гарантии, builder подходит лучше. Если вам нужны только именованные ключи со значениями по умолчанию, объект опций проще.

Да, но будьте осторожны. После вызова build builder все еще хранит состояние из предыдущей конфигурации. Вы должны сбросить каждое поле или создать новый экземпляр builder'а для каждого объекта. Создание нового экземпляра каждый раз — более безопасный и предсказуемый подход.

Может добавлять. Если ваш объект имеет только несколько хорошо известных полей и не требует правил валидации, простой объектный литерал или фабричная функция с именованными параметрами будут чище. Паттерн Builder окупается, когда растет количество необязательных полей, когда значения по умолчанию взаимодействуют друг с другом или когда создание требует обязательных ограничений.

Object.freeze предотвращает изменение свойств верхнего уровня возвращаемого объекта после создания. Это делает построенный результат предсказуемым и доступным только для чтения, что особенно полезно, когда объект передается через несколько слоев кода. Это проводит четкую границу между временем конфигурации внутри builder'а и временем использования вне его.

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay