Подтвердить что ты не робот

Шаблон PHP Builder без внутренних классов

Я читал "Эффективную Java" Джошуа Блоха. Я также разрабатываю PHP, и я хотел бы реализовать шаблон построитель, описанный в пункте 2, но PHP не имеет внутренних классов. Есть ли способ получить этот шаблон в PHP, сохраняя конструктор для продукта частным?

4b9b3361

Ответ 1

Так как PHP не поддерживает внутренние классы, должен быть общедоступный метод в классе продукта, который создает экземпляр его. Рассмотрим следующие классы PHP:

<?php
class NutritionalFactsBuilder {
    private $sodium;
    private $fat;
    private $carbo;

    /**
     * It is preferred to call NutritionalFacts::createBuilder
     * to calling this constructor directly.
     */
    function __construct($s) {
        $this->sodium = $s;
    }

    function fat($f) {
        $this->fat = $f;
        return $this;
    }

    function carbo($c) {
        $this->carbo = $c;
        return $this;
    }

    function getSodium() {
        return $this->sodium;
    }

    function getFat() {
        return $this->fat;
    }

    function getCarbo() {
        return $this->carbo;
    }

    function build() {
        return new NutritionalFacts($this);
    }
}

class NutritionalFacts {
    private $sodium;
    private $fat;
    private $carbo;

    static function createBuilder($s) {
        return new NutritionalFactsBuilder($s);
    }

    /**
     * It is preferred to call NutritionalFacts::createBuilder
     * to calling this constructor directly.
     */
    function __construct(NutritionalFactsBuilder $b) {
        $this->sodium = $b->getSodium();
        $this->fat = $b->getFat();
        $this->carbo = $b->getCarbo();
    }
}

echo '<pre>';
var_dump(NutritionalFacts::createBuilder(10)->fat(23)->carbo(1)->build());
echo '</pre>';
?>

Обратите внимание, что в приведенном выше примере конструктор NutritionalFacts является общедоступным. Из-за ограничений языка, однако, наличие публичного конструктора совсем не плохо. Поскольку нужно вызвать конструктор с NutritionalFactsBuilder, существует только ограниченное число способов создания экземпляра NutritionalFacts. Давайте сравним их:

// NutritionalFacts Instantiation #0
$nfb = new NutritionalFactsBuilder(10);
$nfb = $nfb->fat(23)->carbo(1);
$nf0 = new NutritionalFacts($nfb);

// NutritionalFacts Instantiation #1
$nfb = new NutritionalFactsBuilder(10);
$nf1 = $nfb->fat(23)->carbo(1)->build();

// NutritionalFacts Instantiation #2
$nf2 = NutritionalFacts::createBuilder(10)->fat(23)->carbo(1)->build();

// NutritionalFacts Instantiation #3
// $nf3 = (new NutritionalFactsBuilder(10))->fat(23)->carbo(1)->build();

Чтобы максимально использовать функциональную цепочку, "NutritionalFacts Активация № 2" является предпочтительным использованием.

"NutritionalFacts Активация # 3" показывает еще один нюанс синтаксиса PHP; нельзя привязать метод к недавно созданному объекту. Обновление: В PHP 5.4.0 теперь есть поддержка синтаксиса в "NutritionalFacts Активация # 3." Я еще не тестировал его.


Создание частного конструктора

Вы можете сделать конструктор закрытым, но я бы не рекомендовал его. Если конструктор был закрыт, необходим публичный, статический метод factory, как в следующем фрагменте кода. Рассматривая приведенный ниже код, мы могли бы также сделать конструктор общедоступным, а не вводить косвенность, чтобы сделать конструктор закрытым.

class NutritionalFacts {
    private $sodium;
    private $fat;
    private $carbo;

    static function createBuilder($s) {
        return new NutritionalFactsBuilder($s);
    }

    static function createNutritionalFacts($builder) {
        return new NutritionalFacts($builder);
    }

    private function __construct($b) {
        $this->sodium = $b->getSodium();
        $this->fat = $b->getFat();
        $this->carbo = $b->getCarbo();
    }
}

Ответ 2

В описании Gang of Four шаблона Builder вы не найдете требования к внутреннему классу. Ключевой особенностью является совокупная взаимосвязь между интерфейсом Director и Builder, которые обеспечивают "план" для объединения серии реализаций продукта.

Здесь вы можете найти множество примеров шаблона PHP Builder:

http://www.php5dp.com/category/design-patterns/builder/

Cheers, Билл

Ответ 3

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

Как сказано, существует простой способ реализовать шаблон построителя для создания неизменяемых объектов даже без внутренних классов (хотя теперь он доступен с PHP 7).

Первый важный строительный блок - это общий базовый класс для реального неизменяемого класса и строителя. Это позволяет им получать доступ к свойствам других объектов. Что-то, что также называется классами друзей или разрешено с помощью расширенных модификаторов доступа на других языках, что-то не имеет PHP. Обратите внимание, что способность клона ограничена, нет смысла клонировать неизменяемые объекты, но больше о модификаторе protected позже.

abstract class NutritionalFactData {

    protected $sodium = 0;
    protected $fat = 0;
    protected $carbo = 0;

    protected function __clone() {}

}

Неизменяемый класс прямо с глупыми примерами getters и частным конструктором. Обратите внимание на модификатор final для самого класса и что он вообще не знает класс строителя.

final class NutritionalFacts extends NutritionalFactData {

    public function getSodium() {
        return $this->sodium;
    }

    public function getFat() {
        return $this->fat;
    }

    public function getCarbo() {
        return $this->carbo;
    }

    private function __construct() {}

}

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

final class NutritionalFactBuilder extends NutritionalFactData {

    private $nutritional_facts;

    public function __construct() {
        $this->nutritional_facts = new NutritionalFacts;
    }

    public function build() {
        return clone $this->nutritional_facts;
    }

    public function setSodium($sodium) {
        $this->nutritional_facts->sodium = $sodium;
        return $this;
    }

    public function setFat($fat) {
        $this->nutritional_facts->fat = $fat;
        return $this;
    }

    public function setCarbo($carbo) {
        $this->nutritional_facts->carbo = $carbo;
        return $this;
    }

}

Для полноты примера использования:

var_dump(
    (new NutritionalFactBuilder)
        ->setSodium(21)
        ->setFat(42)
        ->build()
);

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

abstract class CarParts {}

final class Car extends CarParts {}

abstract class CarBuilder extends CarParts {
    abstract public function build(): Car;
}

final class CompactCarBuilder extends CarBuilder {}

final class SportsCarBuilder extends CarBuilder {}

final class RaceCarBuilder extends CarBuilder {}