生成器模式
当我们回顾工厂设计模式时,我们看到了它们在实现多态性方面的作用。工厂模式与构造器模式的关键区别在于,构造器模式的唯一目的是解决一个反模式问题,而不是实现多态性。这个反模式就是伸缩构造器(Telescoping Constructor)。
伸缩构造器问题主要是指构造器所包含的参数数量增长到一定程度,就无法使用,甚至无法知道参数的顺序。
假设我们有一个披萨(Pizza
)类,它主要包含一个构造函数和一个 show
函数,显示函数详细说明了披萨的大小和配料。这个类看起来是这样的
<?php
class Pizza
{
private $size;
private $cheese;
private $pepperoni;
private $bacon;
public function __construct($size, $cheese, $pepperoni, $bacon)
{
$this->size = $size;
$this->cheese = $cheese;
$this->pepperoni = $pepperoni;
$this->bacon = $bacon;
}
public function show()
{
$recipe = $this->size . " inch pizza with the following toppings: ";
$recipe .= $this->cheese ? "cheese, " : "";
$recipe .= $this->pepperoni ? "pepperoni, " : "";
$recipe .= $this->bacon ? "bacon, " : "";
return $recipe;
}
}
注意构造函数包含多少个参数,它实际上包含了大小和每个配料。我们可以做得更好。事实上,让我们把所有参数添加到一个构建器对象中,然后我们可以用它来创建披萨,这样就可以构造披萨了。这就是我们的目标:
$pizzaRecipe = (new PizzaBuilder(9))
->cheese(true)
->pepperoni(true)
->bacon(true)
->build();
$order = new Pizza($pizzaRecipe);
要做到这一点并不难;事实上,你甚至会发现它是我们在这里学到的最简单的设计模式之一。首先,让我们为披萨制作一个生成器,将这个类命名为 PizzaBuilder
:
Unresolved include directive in modules/ROOT/pages/ch03/ch3-06.adoc - include::example$/Chapter 3/Builder/PizzaBuilder.php[]
这个类并不难理解,我们有一个构造函数来设置大小,然后对于我们想要添加的每一个额外配料,我们只需调用相关的配料方法,并相应地将参数设置为 true
或 false
。如果配料方法没有被调用,相关的配料就不会被设置为参数。
最后,我们还有一个构建方法,调用该方法可以在将数据发送到 Pizza
类的构造函数之前,运行最后一分钟的逻辑来组织数据。尽管如此,我通常不喜欢这样做,因为如果方法需要按照特定的顺序进行,这可能会被认为是顺序耦合,这从本质上违背了我们创建一个构建器来完成类似任务的初衷。
因此,每个配料方法都会返回它们所创建的对象,这样,任何函数的输出都可以直接注入到我们想用来构建的类中。
接下来,让我们调整披萨(Pizza
)类,以便使用这个构建器:
<?php
class Pizza
{
private $size;
private $cheese;
private $pepperoni;
private $bacon;
public function __construct(PizzaBuilder $builder)
{
$this->size = $builder->size;
$this->cheese = $builder->cheese;
$this->pepperoni = $builder->pepperoni;
$this->bacon = $builder->bacon;
}
public function show()
{
$recipe = $this->size . " inch pizza with the following toppings: ";
$recipe .= $this->cheese ? "cheese, " : "";
$recipe .= $this->pepperoni ? "pepperoni, " : "";
$recipe .= $this->bacon ? "bacon, " : "";
return $recipe;
}
}
对于构造函数来说,这非常简单;我们只需在需要时访问构造器中的 public
属性即可。
请注意,我们可以在构造函数中对构建器提供的数据添加额外的验证,当然也可以在设置构建器中的方法时添加验证,这取决于所需的逻辑类型。
现在,我们可以将所有这些内容整合到我们的 index.php
文件中:
Unresolved include directive in modules/ROOT/pages/ch03/ch3-06.adoc - include::example$/Chapter 3/Builder/index.php[]
我们应该得到的输出看起来像这样:

构建器设计模式非常容易采用,但在构建对象时可以省去很多麻烦。
这种方法的缺点是每个类都需要一个单独的构造器;这是对对象构造过程进行控制的代价。
除此之外,Builder 设计模式允许你改变构造函数变量,还能很好地封装构造对象本身的代码。与所有设计模式一样,您可以自行决定在代码中使用每种模式的最合适位置。
传统上,键值数组经常被用来替代生成器类。然而,构建器类可以让你对构建过程有更多的控制。
还有一件事我要提一下;在这里,我们只是使用 index.php
方法引用了方法;通常,我们在那里运行的方法都放在一个类中,这个类可以被称为 Director
类。
在此之上,如果你的生成器会有很多逻辑,你也可以考虑在生成器中应用一个接口来实现。