Builder Pattern — Строитель
Пошаговое построение объекта через чейнинг (
obj.setX.setY.build). В JS — очень популярный стиль.
Проблема
Сложный объект с десятками опций. Конструктор с 15 параметрами нечитаем: new Request(url, method, headers, body, timeout, retries, cache). Нужно явно указывать, что задаётся: таймаут, логгер, retry-политика и т.д. Builder также упрощает валидацию: проверки в build гарантируют корректность итогового объекта.
Где используется
- Построение HTTP-запросов
- Генераторы SQL-запросов:
db.select.from.where.limit - Конфигурация сложных объектов: webpack config, тест-фикстуры
- Form builder: динамическое создание форм
- Email/PDF-генераторы
Решение
- Каждый шаг — отдельный метод, возвращающий
this(чейнинг) - В конце —
.buildвозвращает финальный объект - Может быть асинхронным:
await new Builder.setX.build
Реализации
Query Builder (классический)
class QueryBuilder {
#table = '';
#conditions = ;
#fields = ['*'];
#limitValue = null;
#orderByField = null;
from(table) { this.#table = table; return this; }
select(...fields) { this.#fields = fields; return this; }
where(condition) { this.#conditions.push(condition); return this; }
orderBy(field) { this.#orderByField = field; return this; }
limit(n) { this.#limitValue = n; return this; }
build {
if (!this.#table) throw new Error('Таблица не указана');
let sql = `SELECT ${this.#fields.join(', ')} FROM ${this.#table}`;
if (this.#conditions.length) sql += ` WHERE ${this.#conditions.join(' AND ')}`;
if (this.#orderByField) sql += ` ORDER BY ${this.#orderByField}`;
if (this.#limitValue) sql += ` LIMIT ${this.#limitValue}`;
return sql;
}
}
const query = new QueryBuilder
.from('users')
.select('id', 'name', 'email')
.where('active = true')
.orderBy('name')
.limit(10)
.build;
// SELECT id, name, email FROM users WHERE active = true ORDER BY name LIMIT 10
HTTP Client Builder
class HttpClientBuilder {
setTimeout(ms) { this.timeout = ms; return this; }
setLogger(l) { this.logger = l; return this; }
setRetry(n) { this.retry = n; return this; }
build {
return new HttpClient(this.timeout, this.logger, this.retry);
}
}
const client = new HttpClientBuilder
.setTimeout(5000)
.setLogger(console)
.setRetry(3)
.build;
Функциональный билдер (через замыкание)
function createRequestBuilder(baseUrl) {
const config = { url: baseUrl, method: 'GET', headers: {}, body: null, timeout: 5000 };
const builder = {
method(m) { config.method = m; return builder; },
header(k, v) { config.headers[k] = v; return builder; },
body(data) {
config.body = JSON.stringify(data);
config.headers['Content-Type'] = 'application/json';
return builder;
},
timeout(ms) { config.timeout = ms; return builder; },
build { return { ...config }; } // копия, чтобы дальнейшие изменения не повлияли
};
return builder;
}
Где используется в JS-экосистеме
- Knex.js, TypeORM — query builder для SQL
- D3.js — chainable API для визуализации
- jQuery —
$('.item').addClass('x').on('click', ...)— chainable style - fluent assertion в test-фреймворках:
expect(x).to.be.equal(y)
Подводные камни
- Чейнинг затрудняет breakpoints в дебагере — нет промежуточных переменных.
- Не для immutable объектов: если каждый шаг создаёт новый объект, билдер усложняется.
- Возврат
thisломает, если метод должен вернуть что-то другое — нужны parallel APIs. - Не возвращать
this— безreturn thisцепочка прерывается с TypeError. - Мутация конфигурации после
build: если Builder не делает копию приbuild, изменение config после может испортить уже созданный объект. - Builder для простых объектов — избыточный overhead. Используйте объект-опций в конструкторе.
Главные тезисы автора
- «Чейнинг — стиль JavaScript», билдер вписывается естественно.
- Query Builder — золотая середина между ORM (скрывает) и сырым SQL (нужно ручное).
- Билдер может быть асинхронным —
await new Builderработает.
🎓 Источники
- 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
- Builder и стиль чейнинга
- Query Builder как типичный билдер
- Альтернатива ORM — ручное управление + автоматизация
- refactoring.guru — Builder