没有内部类的 PHP 生成器模式


PHP Builder pattern without inner classes

我一直在阅读Joshua Bloch的Effective Java。我也用PHP开发,我想实现第2项中概述的构建器模式,但是PHP没有内部类。有没有办法在PHP中实现这种模式,保持产品的构造函数私有?

由于 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 Instantiation #3" 显示了 PHP 语法的另一个细微差别;不能在新实例化的对象上链接方法更新:在 PHP 5.4.0 中,现在支持"NutritionalFacts实例化 #3"中的语法。不过我还没有测试过。


使构造函数私有

您可以将构造函数设为私有,但我不推荐它。如果构造函数是私有的,则需要一个公共的静态工厂方法,如以下代码片段所示。查看下面的代码,我们不妨将构造函数设为公共,而不是仅仅为了使构造函数私有而引入间接寻址。

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();
    }
}

不变性是好的,绝对是要努力实现的,这适用于PHP,就像它适用于任何其他语言一样。不变性使您可以确定您不必担心实例在您不知情的情况下突然发生变异。

话虽如此,即使没有内部类,也有一种简单的方法可以实现构建器模式来构建不可变的对象(尽管现在在 PHP 7 中可用)。

第一个重要的构建块是实际不可变类和生成器的公共基类。这允许他们访问彼此的属性。也称为友元类或可通过其他语言中的扩展访问修饰符解决的东西,这是 PHP 所没有的。请注意,克隆能力受到限制,克隆不可变对象没有意义,但稍后会详细介绍protected修饰符。

abstract class NutritionalFactData {
    protected $sodium = 0;
    protected $fat = 0;
    protected $carbo = 0;
    protected function __clone() {}
}

不可变类是直接的,带有愚蠢的示例getter和默认构造函数。请注意类本身的final修饰符,并且它根本不知道生成器类。

final class NutritionalFacts extends NutritionalFactData {
    public function getSodium() {
        return $this->sodium;
    }
    public function getFat() {
        return $this->fat;
    }
    public function getCarbo() {
        return $this->carbo;
    }
}

现在是实际的构建器实现。请注意我们如何直接在不可变类的实例上操作,并且我们只是在调用构建方法时克隆它。这可确保以后对构建器的 setter 的调用不会更改以前构建的实例,并确保此类实例的接收方不必自行处理克隆。

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 {}

在四人帮对构建器模式的描述中,您会发现不需要内部类。关键功能是控制器和生成器接口之间的聚合关系,这些接口为组合一系列产品实现提供了"蓝图"。

你可以在这里找到很多 PHP Builder 模式的例子:

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

干杯法案