15

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

4

3 回答 3

36

由于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是公共的。然而,由于语言的限制,拥有一个公共构造函数一点也不坏。由于必须使用 a 调用构造函数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”中的语法。虽然我还没有测试过。


使构造函数私有

您可以将构造函数设为私有,但我不建议这样做。如果将构造函数设为私有,则需要一个公共的静态工厂方法,如以下代码片段所示。看下面的代码,我们不妨将构造函数设为公开,而不是仅仅为了让构造函数私有而引入间接。

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();
    }
}
于 2012-06-09T17:53:24.317 回答
4

不变性是好的,而且绝对值得努力,这适用于 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;
    }

}

现在是实际的构建器实现。请注意我们如何直接对不可变类的实例进行操作,并且在调用 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 {}
于 2016-08-11T20:13:20.160 回答
2

在 Builder 模式的四人帮描述中,您会发现不需要内部类。关键特性是 Director 和 Builder 接口之间的聚合关系,它为组合一系列 Product 实现提供了“蓝图”。

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

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

干杯,比尔

于 2014-07-30T10:05:34.740 回答