Дисциплины - Объектно-ориентированное программирование

Шаблоны проектирования практических задач - Порождающие шаблоны проектирования - Строитель

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

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

Шаблон предлагает разбить процесс конструирования объекта на отдельные шаги (например, построитьСтены , вставитьДвери и другие). Чтобы создать объект, вам нужно поочерёдно вызывать методы строителя. Причём не нужно запускать все шаги, а только те, что нужны для производства объекта определённой конфигурации.

Строитель позволяет создавать сложные объекты пошагово.

Реализация в JavaScript

Строитель - позволяет создавать сложные объекты пошагово. Строитель позволяет использовать один код для получения разных объектов.

Зачем использовать - отделить сложную логику создания от финального представления.

class ProductBuilder {
    constructor() {
        this.name = 'A Product';
        this.price = 9.99;
        this.category = 'other';
    }

    withName(name) {
        this.name = name;
        return this;
    }

    withPrice(price) {
        this.price = price;
        return this;
    }

    withCategory(category) {
        this.category = category;
        return this;
    }

    build() {
        return {
            name: this.name,
            price: this.price,
            category: this.category,
        }
    }
}
console.log(
  new ProductBuilder()
    .withName('Harry Potter')
    .withCategory('book')
    .build()
)
// =>
//    {
//      name: 'Harry Potter',
//      price: 9.99,
//      category: 'book'
//    }

Обратите внимание, что при создании экземпляра мы не используем сложный вызов в конструкторе. Это нерационально, если, например, у вас объект имеет под 100 свойство.

Если объект простой, то шаблон строитель усложнит код.

В чем отличие строителя от фабрики: шаблон строитель позволяет конфигурировать объект, фабрика же создает объект за один шаг. То есть фабрика, как правило, не конфигурирует объект при его создании.

Вот еще пример с более высокой абстракцией director...

Допустим, на сайте присутствует баннер. В зависимости от размера окна, в котором отображается сайт, баннер имеет разные характеристики: ширину, высоту, изображение. Со временем могут появиться новые характеристи, это следует учесть, чтобы сделать легко обслуживаемый код. Решим задачу с помощью паттерна "Строитель".

Для начала создадим класс баннера.

class Banner {
    constructor(params) {
        this.url = params.url;
        this.lowResImg = params.lowResImg;
        this.highResImg = params.highResImg;
    }
    setWidth(width) {
        this.width = width;
    }
    setHeight(height) {
        this.height = height;
    }
    setImg(img) {
        this.currentImg = img;
    }
    render() {
        let banner = document.getElementById("banner");
        banner.style.width = this.width + "px";
        banner.style.height = this.height + "px";
        banner.setAttribute('src', this.currentImg);
        banner.setAttribute('data-url', this.url);
        document.body.appendChild(banner);
    }
}

В нашем примере, класс Banner имеет метод render(), который получает элемент с id="banner" и отрисовывает его в зависимости от параметров.
Теперь создадим базовый строитель и реализации строителей для низкого и высокого разрешения.

class BuilderBanner {
    constructor(params) {
        this.banner = new Banner(params);
    }
    renderBanner() {
        this.banner.render();
    }
}


class BuilderLowResBanner extends BuilderBanner {
    setWidth(width) {
        this.banner.setWidth(320);
    }
    setHeight(height) {
        this.banner.setHeight(100);
    }
    setImg(img) {
        this.banner.setImg(this.banner.lowResImg);
    }
}


class BuilderHighResBanner extends BuilderBanner {
    setWidth(width) {
        this.banner.setWidth(820);
    }
    setHeight(height) {
        this.banner.setHeight(400);
    }
    setImg(img) {
        this.banner.setImg(this.banner.highResImg);
    }
}

Осталось реализовать директора, для управления строителями.

class DirectorBanner {
    constructor(builder) {
        if (! builder instanceof BuilderBanner) {
            throw "not builder object passed";
        }
        this.builder = builder;
    }
    constructBanner() {
        this.builder.setWidth();
        this.builder.setHeight();
        this.builder.setImg();
    }
    renderBanner() {
        this.builder.renderBanner();
    }
}

Теперь можно написать реализацию работы с данными классами. Так выглядит клиентский код:

initBanner(); 
// первичная инициализация

window.onresize = () => initBanner(); 
// перерисовка при масштабировании страницы

//функция инициализации:
function initBanner() {
    const bannerParams = {
        url: "/some-page.html",
        lowResImg: "awesome-mob.jpeg",
        highResImg: "avesome-desc.jpeg"
    };
    let bulder = window.innerWidth > 819 
        ? new BuilderHighResBanner(bannerParams)
        : new BuilderLowResBanner(bannerParams);
    let director = new  DirectorBanner(bulder);
    director.constructBanner();
    director.renderBanner();
}

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

В неструктурированном коде сложно разобраться и поддерживать. Шаблон "Строитель" значительно упрощает клиентскую часть кода, при этом его классы и методы имеют ясное назначение и в них будет легко разобраться и внести необходимые изменение даже спустя длительное время.

Реализация в PHP

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

/**
 * Director is part of the builder pattern. It knows the interface of the builder
 * and builds a complex object with the help of the builder
 *
 * You can also inject many builders instead of one to build more complex objects
 */
class Director
{
    public function build(Builder $builder): Vehicle
    {
        $builder->createVehicle();
        $builder->addDoors();
        $builder->addEngine();
        $builder->addWheel();
        return $builder->getVehicle();
    }
}

Класс Director - это ключевой класс шаблона строитель, задача которого определить метод строителя.

Следующий элемент шаблона - интерфейс Builder.php

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

interface Builder
{
    public function createVehicle();

    public function addWheel();

    public function addEngine();

    public function addDoors();

    public function getVehicle(): Vehicle;
}

TruckBuilder.php

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

class TruckBuilder implements Builder
{
    /**
     * @var Parts\Truck
     */
    private $truck;

    public function addDoors()
    {
        $this->truck->setPart('rightDoor', new Parts\Door());
        $this->truck->setPart('leftDoor', new Parts\Door());
    }

    public function addEngine()
    {
        $this->truck->setPart('truckEngine', new Parts\Engine());
    }

    public function addWheel()
    {
        $this->truck->setPart('wheel1', new Parts\Wheel());
        $this->truck->setPart('wheel2', new Parts\Wheel());
        $this->truck->setPart('wheel3', new Parts\Wheel());
        $this->truck->setPart('wheel4', new Parts\Wheel());
    }

    public function createVehicle()
    {
        $this->truck = new Parts\Truck();
    }

    public function getVehicle(): Vehicle
    {
        return $this->truck;
    }
}

CarBuilder.php

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

class CarBuilder implements Builder
{
    /**
     * @var Parts\Car
     */
    private $car;

    public function addDoors()
    {
        $this->car->setPart('rightDoor', new Parts\Door());
        $this->car->setPart('leftDoor', new Parts\Door());
        $this->car->setPart('trunkLid', new Parts\Door());
    }

    public function addEngine()
    {
        $this->car->setPart('engine', new Parts\Engine());
    }

    public function addWheel()
    {
        $this->car->setPart('wheelLF', new Parts\Wheel());
        $this->car->setPart('wheelRF', new Parts\Wheel());
        $this->car->setPart('wheelLR', new Parts\Wheel());
        $this->car->setPart('wheelRR', new Parts\Wheel());
    }

    public function createVehicle()
    {
        $this->car = new Parts\Car();
    }

    public function getVehicle(): Vehicle
    {
        return $this->car;
    }
}

Parts/Vehicle.php

namespace DesignPatterns\Creational\Builder\Parts;

abstract class Vehicle
{
    /**
     * @var object[]
     */
    private $data = [];

    /**
     * @param string $key
     * @param object $value
     */
    public function setPart($key, $value)
    {
        $this->data[$key] = $value;
    }
}

Parts/Truck.php

namespace DesignPatterns\Creational\Builder\Parts;

class Truck extends Vehicle
{
}

Parts/Car.php

namespace DesignPatterns\Creational\Builder\Parts;

class Car extends Vehicle
{
}

Parts/Engine.php

namespace DesignPatterns\Creational\Builder\Parts;

class Engine
{
}

Parts/Wheel.php

declare(strict_types=1);

namespace DesignPatterns\Creational\Builder\Parts;

class Wheel
{
}

Parts/Door.php

namespace DesignPatterns\Creational\Builder\Parts;

class Door
{
}

Тест. Tests/DirectorTest.php

declare(strict_types=1);

namespace DesignPatterns\Creational\Builder\Tests;

use DesignPatterns\Creational\Builder\Parts\Car;
use DesignPatterns\Creational\Builder\Parts\Truck;
use DesignPatterns\Creational\Builder\TruckBuilder;
use DesignPatterns\Creational\Builder\CarBuilder;
use DesignPatterns\Creational\Builder\Director;
use PHPUnit\Framework\TestCase;

class DirectorTest extends TestCase
{
    public function testCanBuildTruck()
    {
        $truckBuilder = new TruckBuilder();
        $newVehicle = (new Director())->build($truckBuilder);

        $this->assertInstanceOf(Truck::class, $newVehicle);
    }

    public function testCanBuildCar()
    {
        $carBuilder = new CarBuilder();
        $newVehicle = (new Director())->build($carBuilder);

        $this->assertInstanceOf(Car::class, $newVehicle);
    }
}

Количество комментариев: 0

Для того, чтобы оставить коментарий необходимо зарегистрироваться
814301 БГУИР
814302 БГУИР
814303 БГУИР
894351 БГУИР
90421 БГУИР


Изображения Видео

1. Абстрактная фабрика https://www.youtube.com/watch?v=1mVONOCxfLg
2. Фабричный метод https://www.youtube.com/watch?v=5UqUDR6_2cY
3. Шаблон декоратор https://www.youtube.com/watch?v=Lwb9bm8yKD0
4. Dessign patterns on PHP https://github.com/domnikl/DesignPatternsPHP
5. Приёмы объектно-ориентированного проектирования. Паттерны проектирования Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес; [пер. с англ.: А. Слинкин науч. ред.: Н. Шалаев]. — Санкт-Петербург [и др.] : Питер, 2014. — 366 с. : ил. ; 24 см.
6. Приемы объектно-ориентированного проектирования. Паттерны проектирования Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес; [пер. с англ.: А. Слинкин науч. ред.: Н. Шалаев]. — Санкт-Петербург [и др.] : Питер, 2014. — 366 с. : ил. ; 24 см.
7. Ajax http://erud.by/ajax
8. Ajax http://erud.by/ajax
9. Ajax http://erud.by/ajax
10. Документация Laravel http://laravel.com
Задание к курсовой работе
Задание к курсовой работе
Вопросы к экзамену