我一直在阅读Joshua Bloch的Effective Java。我也用PHP开发,我想实现builder pattern outlined in item 2,但PHP没有内部类。有没有办法在PHP中实现这种模式,保持产品的构造函数是私有的?
答案 0 :(得分:32)
从PHP does not support inner classes开始,产品类上必须有一个公共方法来创建它的实例。请考虑以下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”是首选用法。
“ 更新:在PHP 5.4.0中,“NutritionalFacts
Instantiation#3”显示了PHP语法的另一个细微差别; one cannot chain a method on a newly instantiated object. NutritionalFacts
实例化#3”中现在有support for the syntax。我还没有测试过它。
使构造函数成为私有
您可以将构造函数设为私有,但我不推荐它。如果构造函数是私有的,则需要一个公共的静态工厂方法,如下面的代码片段所示。看下面的代码,我们不妨将构造函数设为public而不是引入间接只是为了使构造函数成为私有。
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();
}
}
答案 1 :(得分:2)
在Gang of Four描述的Builder模式中,你不会发现内部类的要求。关键特性是Director和Builder接口之间的聚合关系,它为一系列产品实现提供了“蓝图”。
您可以在此处找到许多PHP Builder模式的示例:
http://www.php5dp.com/category/design-patterns/builder/
干杯, 比尔
答案 2 :(得分:2)
不变性很好,绝对值得努力,这适用于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;
}
private function __construct() {}
}
现在是实际的构建器实现。注意我们如何直接在不可变类的实例上操作,并且我们只是在调用构建方法时克隆它。这可以确保以后对构建器的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 {}