Пример использования паттерна Builder на JavaScript

Паттерн Builder – или «Строитель» – разбивает строение сложных объектов на шаги. То есть позволяет создавать различные варианты объекта, используя один и тот же код.

Рассмотрим на примере возведения зданий. Возьмем за основу, что в каждом здании предусматривается какое-то количество окон, дверей и этажей.

class BuildingBuilder {
constructor() {
this.windows = 0;
this.doors = 1;
this.floors = 1;
}
setWindows(amount) {
this.windows = amount;
return this;
}

setDoors(amount) {
this.doors = amount;
return this;
}

setFloors(amount) {
this.floors = amount;
return this;
}

build() {
return new Building(this)
}

}
class Building {
constructor(props) {

// Проверяем, принадлежит ли полученный объект классу BuildingBuilder
if (!(props instanceof BuildingBuilder)) {
return new Error('Argument is not an instance of BuildingBuilder!')
}
this.extendProperties(props);
}

// Проходим циклом по всем полям полученного класса,
// и присваиваем его свойства классу Building

extendProperties(props) {
for (let prop in props) {
this[prop] = props[prop];
}
}
}

В классе BuildingBuilder есть методы, которые устанавливают количество окон, дверей и этажей. А также метод build(). Он будет возвращать экземпляр класса Building, который принимает конструктор класса BuildingBuilder, наследуя его свойства. Сделаем небольшую проверку на несоответствие ожидаемого аргумента.

Далее. Предположим, что есть заказ на возведение двух абсолютно разных домов. Одноэтажного и небоскреба. В одноэтажном должно быть две двери, четыре окна и бассейн. А в небоскребе – 100 этажей, 2 000 дверей, 3 000 окон и лифт. Понятно, что вышеупомянутые дома отличаются по многим параметрам. Но для понимания паттерна Builder, думаю, этого будет вполне достаточно. Расширим класс «Строителя», после чего возведем оба здания с его помощью:

class BuildingBuilder {

constructor() {
this.windows = 0;
this.doors = 1;
this.floors = 1;
}

setWindows(amount) {
this.windows = amount;
return this;
}

setDoors(amount) {
this.doors = amount;
return this;
}

setFloors(amount) {
this.floors = amount;
return this;
}

setPool() {
this.pool = true;
return this;
}

setElevator() {
this.elevator = true;
return this;
}

build() {
return new Building(this);
}
}


const oneFloorBuilding = new BuildingBuilder()
.setDoors(2)
// Устанавливаем 2 двери
.setWindows(4)
// Устанавливаем 4 окна
.setPool()
// устанавливаем бассейн во дворе
.build();
// строим здание

const skyscraperBuilding = new BuildingBuilder()
.setFloors(100)
// Строим этажи
.setElevator()
// Устанавливаем лифт
.setDoors(2000)
// Устанавливаем двери
.setWindows(3000)
// Устанавливаем окна
.build();
// строим здание

console.log(oneFloorBuilding); // Building { windows: 4, doors: 2, floors: 1, pool: true }
console.log(skyscraperBuilding); // Building { windows: 3000, doors: 2000, floors: 100, elevator: true }

Конечно же, можно было написать класс здания и передать все необходимые параметры в конструктор. А если бы здания отличались не двумя параметрами, а, скажем, десятью? Или понадобилось бы возвести дом с гаражом? Или замок? Или просто-напросто сооружение необычной геометрической формы? Во всех этих ситуациях идея с использованием конструктора не принесет положительных результатов.

Ключевая особенность паттерна «Строитель» – то, что он предоставляет возможность расширять класс без влияния на объекты, которые уже были построены. И не расширяя конструктор класса десятками параметров.

В паттерне Builder применяется еще один уровень абстракции, а именно – Director. Это дополнительный класс, который нужен в ситуациях, когда есть заказы на повторяющиеся здания. К примеру, если часто приходится строить дома с лифтом, восемью этажами, двумястами окнами и сотней дверей.

class BuildingBuilderDirector {

static eightFloorsBuilding() {
return new BuildingBuilder()
.setFloors(8)
// Строим этажи
.setElevator()
// Устанавливаем лифт
.setDoors(100)
// Устанавливаем двери
.setWindows(200)
// Устанавливаем окна
.build();
// строим здание
}

}

const eightFloorsBuilding = BuildingBuilderDirector.eightFloorsBuilding();
console.log(eightFloorsBuilding); // Building { windows: 200, doors: 100, floors: 8, elevator: true }

Как видим, паттерн Builder позволяет создавать объекты одного класса, но с разной структурой.

class BuildingBuilder {

constructor() {
this.windows = 0;
this.doors = 1;
this.floors = 1;
}

setWindows(amount) {
this.windows = amount;
return this;
}

setDoors(amount) {
this.doors = amount;
return this;
}

setFloors(amount) {
this.floors = amount;
return this;
}

setPool() {
this.pool = true;
return this;
}

setElevator() {
this.elevator = true;
return this;
}

build() {
return new Building(this);
}

}

class Building {
constructor(props) {
// Проверяем, принадлежит ли полученный объект классу BuildingBuilder
if (!(props instanceof BuildingBuilder)) {
return new Error('Argument is not an instance of BuildingBuilder!')
}
this.extendProperties(props);
}

// Проходим циклом по всем полям полученного объекта,
// и присваиваем его свойства классу Building

extendProperties(props) {
for (let prop in props) {
this[prop] = props[prop];
}
}
}

class BuildingBuilderDirector {

static eightFloorsBuilding() {
return new BuildingBuilder()
.setFloors(8)
// Строим этажи
.setElevator()
// Устанавливаем лифт
.setDoors(100)
// Устанавливаем двери
.setWindows(200)
// Устанавливаем окна
.build();
// строим здание
}

}

const oneFloorBuilding = new BuildingBuilder()
.setDoors(2)
// Устанавливаем 2 двери
.setWindows(4)
// Устанавливаем 4 окна
.setPool()
// устанавливаем бассейн во дворе
.build();
// строим здание

const skyscraperBuilding = new BuildingBuilder()
.setFloors(100)
// Строим этажи
.setElevator()
// Устанавливаем лифт
.setDoors(2000)
// Устанавливаем двери
.setWindows(3000)
// Устанавливаем окна
.build();
// строим здание

const eightFloorsBuilding = BuildingBuilderDirector.eightFloorsBuilding();

console.log(oneFloorBuilding); // Building { windows: 4, doors: 2, floors: 1, pool: true }
console.log(skyscraperBuilding); // Building { windows: 3000, doors: 2000, floors: 100, elevator: true }
console.log(eightFloorsBuilding); // Building { windows: 200, doors: 100, floors: 8, elevator: true }

Юрий Кизилов, компания Craft Group