NazimovDmitrii
8/27/2019 - 5:27 PM

[JS ES6 Паттерн СТРОИТЕЛЬ (builder)] #js #ES6 #ООП #Паттерны

[JS ES6 Паттерн СТРОИТЕЛЬ (builder)] #js #ES6 #ООП #Паттерны

/**
 *
 * ПАТТЕРН СТРОИТЕЛЬ (builder (композиционный конструктор обьекта))
 *
 * Паттерн СТРОИТЕЛЬ - это порождающий паттерн проектирования, который позволяет создавать сложные объекты пошагово.
 * Строитель даёт возможность использовать один и тот же код строительства для получения разных представлений объектов.
 * Применение: Паттерн Строитель нужен, если объект может существовать в разных вариациях или процесс
 * инстанцирования состоит из нескольких шагов.
 * В этом случае есть три варианта:
 * - огромный конструктор(фабрика) со множеством if/else в котором легко запуться и который тяжело понять
 * - плодить кучу подклассов для всех вариантов (это наверное еще хуже первого варианта)
 * - паттерн строитель (предлагает разбить процесс конструирования объекта на отдельные шаги (например, построитьСтены,
 * вставитьДвери и другие). Чтобы создать объект, вам нужно поочерёдно вызывать методы строителя. Строителей может быть
 * несколько разных вариантов, но у них общий интерфейс.)
 */

class abstract_Products { // абстрактный класс продуктов
    constructor(builder) {
        this.size = builder.size
        this.cheeze = builder.cheeze || false
        this.pepperoni = builder.pepperoni || false
        this.lettuce = builder.lettuce || false
        this.tomato = builder.tomato || false
    }
}

//конкретные продукты
class Burger extends abstract_Products {
    constructor(builder) {
        super(builder);
        this.breadType = builder.breadType;
        this.meatType = builder.meatType;
    }
}

class Pizza extends abstract_Products {
    constructor(builder) {
        super(builder);
        this.doughType = builder.doughType;
    }
}



class ProductBuilder { // абстрактный класс строителя. Методы м.б. как асбтрактными, так и описанными.
    constructor(size) {
        this.size = size
    }

    addPepperoni() {
        this.pepperoni = true
        return this
    }

    addLettuce() {
        this.lettuce = true
        return this
    }

    addCheeze() {
        this.cheeze = true
        return this
    }

    addTomato() {
        this.tomato = true
        return this
    }

    getProduct() { // этот метод абстрактный, т.к. всегда разный
        throw new Error(`В ${this.constructor.name} не описан метод getProduct()`)
    }
}

class BurgerBuilder extends ProductBuilder {
    constructor(size) {
        super(size);
    }

    setBreadType (bread) {
        this.breadType = bread
        return this
    }
    setMeatType (meat) {
        this.meatType = meat
        return this
    }
    getProduct() {
        if (!this.breadType) {
            throw new Error(`${this.constructor.name} получил некорректное значение breadType`)
        } else if (!this.meatType) {
            throw new Error(`${this.constructor.name} получил некорректное значение meatType`)
        }
        return new Burger(this)
    }
}

class PizzaBuilder extends ProductBuilder {
    constructor(size) {
        super(size);
    }

    setDoughType (doughType) {
        this.doughType = doughType;
        return this
    }

    getProduct() {
        if (!this.doughType) {
            throw new Error(`${this.constructor.name} получил некорректное значение doughType`)
        }
        return new Pizza(this)
    }
}


// Клиентский код. При необходимости можно его поместить в отдельный класс - Director
const burger = (new BurgerBuilder(14))
    .addPepperoni()
    .addLettuce()
    .addTomato()
    .setBreadType('black')
    .setMeatType('chicken')
    .getProduct()

console.log(burger);

const pizza = (new PizzaBuilder(32))
    .addPepperoni()
    .addCheeze()
    .addLettuce()
    .addTomato()
    .setDoughType('slim')
    .getProduct()

console.log(pizza);

const slimBurger = (new BurgerBuilder(14)) // делаем еще один бургер, но чтобы результат был другим
    .setBreadType('white')
    .setMeatType('beef')
    .getProduct()

console.log(slimBurger);