没有内部类的PHP构建器模式

问题描述 投票:0回答:3
我一直在通过约书亚·布洛赫(Joshua Bloch)的有效Java阅读。我也在PHP中开发,我想实现项目2

中概述的构建模式,但是PHP没有内部类。是否有任何方法可以在PHP中实现此模式,使产品的构造函数私有?

php design-patterns builder
3个回答
36
投票

<?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();
Intantiation#2”是首选的用法。

NutritionalFacts

实例化#3”显示了PHP语法的另一个细微差别;

一个人不能在新实例化的对象上链接一种方法。eupdate:
php 5.4.0中,现在在“

NutritionalFacts

实例化#3”中使用语法puppart。我还没有测试过。

制造构造函数私有

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

NutritionalFacts
    

Immutability是好的,绝对是值得努力的,这适用于PHP,就像任何其他语言一样。不变性使您可以肯定的是,您不必担心该实例突然在不知情的情况下突然突变。

也就是说,有一种简单的方法可以实现构建器模式以构建不变的对象,即使没有内部类别(尽管现在可以使用PHP 7)。

第一个重要的构建块是实际不变的类和建筑商的共同基类。这使他们可以访问彼此的属性。通过其他语言中的扩展访问修饰符也被称为朋友类或可解决的东西,PHP没有。请注意,克隆能力受到限制,克隆不变的对象没有意义,但更多地涉及
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();
    }
}
修饰符。

protected

不变的班级与愚蠢的示例Getters和默认的构造函数直截了当。请注意类本身的abstract class NutritionalFactData { protected $sodium = 0; protected $fat = 0; protected $carbo = 0; protected function __clone() {} } 修饰符,并且根本不知道建筑商类。

final

现在实际的建筑商实施。请注意,我们如何直接在不变类的实例上运行,并在调用构建方法时只需克隆它即可。这样可以确保后来打电话给建筑商的设置者不会更改以前构建的实例,并确保没有这样的实例的接收者必须自己照顾克隆。
final class NutritionalFacts extends NutritionalFactData {

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

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

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

}

5
投票

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

HERCE是

示例

.

。

我认为很明显,我们现在可以根据需要实施尽可能多的建筑商实施。对于此示例并不需要,但是我们可以考虑涉及更多属性的其他构造。就像Wikipedia的(非常糟糕的)建筑商图案中给出的汽车示例一样。我们可能想为已知的汽车类别提供预先配置的建筑商。

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


在制造商模式的四个描述的帮派中,您会发现对内部类别没有任何要求。关键功能是导演和构建器界面之间的总体关系,该界面提供了一个“蓝图”来整理一系列产品实现。 您可以在这里找到很多PHP构建器模式的示例:

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


教士, 比尔


对于任何寻求解决方案的人,您不必手动通知每个Getter和setter,您可以使用

PHPMagicMethods
。更具体,__ call和__ callstatic方法。
您可以创建以下抽象类:

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 {} 然后您可以创建这样的类

abstract class Builder { /** * This method will check if the property exists on the class then create a new instance and set its value * * @since 0.0.1 * * @return Builder * * @throws \Exception */ public static function __callStatic($name, $arguments) { if (property_exists(static::class, $name)) { return (new static)->__setProperty($name, ...$arguments); } throw new \Exception("Property $name does not exist on ".self::class); } /** * This method will check if the property exists on the class then and set its value * * @since 0.0.1 * * @return Builder * * @throws \Exception */ public function __call($name, $arguments) { if (property_exists(static::class, $name)) { return $this->__setProperty($name, ...$arguments); } throw new \Exception("Property $name does not exist on ".self::class); } /** * Dynamically sets the property on the class * * @since 0.0.1 */ protected function __setProperty(string $propertyName, mixed $value): self { $this->{$propertyName} = $value; return $this; } /** * This method is responsible for creating the object with all the previous declared properties * * @return mixed */ abstract public function build(); }

为了帮助完成自动完成,您只需要声明在构建器类上方使用PHP文档的方法
到目前为止,我确定的注意事项是,如果声明了嵌套的结构或验证属性类型。
    

2
投票

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.