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

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

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

БЫСТРЫЙ ПРИМЕР

Предположим, у нас есть программа, которая создает чай для команды. Рассмотрим фрагмент кода ниже

class Tea {
    constructor(chocolate, milk, sugar, honey, temperature) {
        return chocolate + milk + sugar + honey + temperature;
    }
}

Этот фрагмент кода создает чашку чая для потребителя. Для простоты предположим очень простой процесс. Смешайте все ингредиенты вместе и отправляйтесь в путь.

На первый взгляд это кажется очень простым. Затем наступает время, когда эта программа будет использоваться, возможно, третьей стороной или даже нами самими через пару месяцев, и мы начинаем сталкиваться с более тонкими проблемами, касающимися деталей, таких как округление температуры до 2 или 3 знаков после запятой? или что на первом месте... мед или сахар? Хотя сейчас нам может быть легко просто вернуться назад и просмотреть определение конструктора класса, у нас может не быть такой роскоши постоянно. Это пример, когда мы можем использовать строитель.

В некотором смысле, подумайте о строителе таким образом;

БИЛДЕРЫ МОЖНО ИСПОЛЬЗОВАТЬ ДЛЯ АБСТРАКТИРОВАНИЯ ДЕТАЛИ РЕАЛИЗАЦИИ КОНСТРУКЦИИ МЕЛКИХ ОБЪЕКТОВ И ПРЕДСТАВЛЕНИЯ ОБЩЕГО ИНТЕРФЕЙСА ПОЛЬЗОВАТЕЛЮ.

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

В настоящее время для создания экземпляра класса Tea нам нужно выполнить следующий код:

пусть MyTea = новый чай (23, ноль, 5, 3, 23,45);

Однако, используя шаблон построителя, мы можем реорганизовать класс Tea следующим образом;

class Tea {
    constructor(chocolate) { // constructor now takes an argument. We could implement a zero-parameter constructor if we desire.
        this._chocolate = chocolate;
        this._milk = null;
        this._sugar = null;
        this._honey = null;
        this._temperature = null;
}
    addMilk (quantity) {
// we can apply transformations to the value here, much like using a setter
        this._milk = quantity;
        return this; // this is the line that does all the magic. I will explain further in a bit
    }
    addSugar (quantity) {
        this._sugar = quantity;
        return this;
    }
    addHoney (quantity) {
        this._honey = quantity;
        return this;
    }
    setTemperature (value) {
        let temperature = Number.parseFloat(value); // like I said, we can control how the passed values are injected into the application using this
        this._temperature = temperature;
        return this;
}
    brewCup () {
        return this._chocolate + this._milk + this._honey + this._sugar + this._temperature;
    }
}

Теперь, чтобы заварить чашку чая, мы могли бы сделать это таким образом.

let MyTea = new Tea(‘Choco’);
myTea.addMilk(‘milk’).addHoney(‘honey’).addSugar(‘sugar’).setTemperature(23.918).brewCup();

Обратите внимание, что порядок вызова методов не имеет большого значения для конечного продукта? Это связано с тем, что шаблон построителя постоянно возвращает экземпляр класса построителя, и этот экземпляр класса всегда будет предоставлять все свои методы, доступные для вызова потребителем в любой момент. Вы можете буквально сделать .addMilk().addMilk().addMilk(), и он сработает, потому что this, возвращаемый методами, всегда будет нести методы вместе с ними.

Другой способ выполнения шаблона построителя заключается в использовании абстрактных классов и конкретных классов. Однако в JavaScript нет концепции абстрактных или конкретных сущностей, поэтому у нас есть ограниченные конструкции для работы, если мы не имитируем абстрактную функциональность. Однако идея состоит в том, что у вас есть CREATOR, класс STENCIL/TEMPLATE, называемый классом ABSTRACT, и вы создаете экземпляр класса или объекта TARGET.

Почему это может быть лучшим подходом? В какой-то момент классы TARGET также могут стать достаточно большими, чтобы их можно было абстрагировать в отдельные собственные классы. Чтобы проиллюстрировать этот подход к шаблону строителя, мы можем использовать в качестве примера производственную линию автомобильной компании.

Предположим, я открываю компанию, которая будет обслуживать многие виды транспорта в Китае. Вот мы и собираемся производить велосипеды, автомобили и корабли в очень больших количествах. Нам нужно наладить производственные линии. Это линии, которые будут выпускать продукцию.

Каждая линия должна обслуживать определенный вид транспорта. Таким образом, мы устанавливаем 3 производственные линии в общей сложности.

Предполагая, что у нас есть фабрика;

class VehicleFactory {
    constructor(builder) {
        this._builder = builder
    }
    build () {
        this._builder.step1();
        this._builder.step2();
        this._builder.step3();
        return this._builder.getBuild();
    }
}

Это простая фабрика. Здесь не так много деталей, хотя фабрики могут быть более сложными. Однако для целей этого урока допустим, что наша фабрика так проста.

Мы видим, что эта фабрика предоставляет метод сборки, который затем взаимодействует со сборщиком, которым мы инициализировали наш класс, и выдает продукт. Мы также можем увидеть здесь предостережение: все наши абстрактные классы должны предоставлять методы с именами step1, step2, step3 и getBuild. Однако мы можем наслаждаться абстракцией, которую мы получаем, когда мы можем создавать отдельные абстрактные классы, мы получаем лучший контроль над классами, поскольку они меньше, их легче понять и о них легче думать.

class BicycleFactory {
    constructor(product) {
        this._product = product;
    }
    step1 () {
        return 'Add 2 tyres'
    }
    step2 () {
        return 'Add handlebar controls'
    }
    step3 () {
        return 'Add manual power'
    }
    getBuild () {
        return 'Build'
    }
}
class CarFactory {
    constructor(product) {
        this._product = product;
    }
    step1 () {
        return 'Add 4 tyres'
    }
    step2 () {
        return 'Add steering controls'
    }
    step3 () {
        return 'Add petrol power'
    }
    getBuild () {
        return 'Build'
    }
}
class ShipFactory {
    constructor(product) {
        this._product = product;
    }
    step1 () {
        return 'Add floatation technology'
    }
    step2 () {
        return 'Add rudder controls'
    }
    step3 () {
        return 'Add diesel power'
    }
    getBuild () {
        return 'Build'
    }
}

Мы видим, что все три фабрики предоставляют один и тот же интерфейс. Это позволяет нашей абстрактной фабрике адаптироваться к нашей фабрике и создавать конкретные продукты. Теперь мы можем сказать

let AbstractCar = new CarFactory(‘car’);
let AbstractBicycle = new BicycleFactory(‘bicycle’);
let AbstractShip = new ShipFactory(‘ship’);
let CarShop = new VehicleFactory(AbstractCar);
let BicycleShop = new VehicleFactory(AbstractBicycle);
let ShipShop = new VehicleFactory(AbstractShip);

Затем мы можем получить наши конкретные классы, вызвав:

CarShop.build();
BicycleShop.build();
ShipShop.build();

Теперь мы снова абстрагируем создание конкретных классов от фабрики. Мы успешно отделили процесс создания (фактическая фабрика) от того, как создается продукт (абстрактная фабрика);

Это то, что представляет собой шаблон построителя и как его можно успешно реализовать с помощью Javascript.

ДАЛЬНЕЙШЕЕ ЧТЕНИЕ

  1. Углубленное понимание наследования — OpenGenus
  2. Разница между абстрактными классами и конкретными классами — Geeksforgeeks