生成器
意图
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
生成器针对是「复杂的对象」,例如,一辆汽车包括了座位、引擎、GPS、轮胎、车灯 … 等,我们使用构造参数来构建
<?php
class Car
{
private $seat;
private $engine;
private $gps;
private $tire;
private $light;
private $steering;
public function __construct($seat, $engine, $gps, $tire, $light, $steering)
{
$this->seat = $seat;
$this->engine = $engine;
$this->gps = $gps;
$this->tire = $tire;
$this->light = $light;
$this->steering = $steering;
}
}
这种方式会大大增加对象的复杂性,同时相关的参数分散在客户端,不利于管理和维护。我们使用生成器来进行重构。生成器的设计模式包括了两点:
- 做法 - 将一个复杂对象的构建与它的表示分离
- 作用 - 使得同样的构建过程可以创建不同的表示
首先是如何将一个复杂对象的构建与它的表示分离。使用 Builder 来代表对象的构建接口
<?php
interface Builder
{
public function setSeats($number);
public function setEngine($engine);
public function setLight($type);
public function setGps();
public function setSteering($type);
}
对象 Car 只负责表示层,不负责对象的构建,这样就实现了对象构建和表示的分离
<?php
class Car
{
public $parts = [];
public function listParts(): void
{
echo "该汽车的组成部分为" . implode(', ', $this->parts) . "\n\n";
}
}
CarBuilder 负责 Car 的构建。注意,CarBuilder 需要提供一个产品检索的接口,这里我们用 getCar
表示
<?php
class CarBuilder implements Builder
{
protected $car;
public function setSeats($number)
{
$this->car->parts[] = '构建座位';
}
public function setEngine($engine)
{
$this->car->parts[] = '构建引擎';
}
public function setLight($type)
{
// TODO: Implement setLight() method.
}
public function setGps()
{
// TODO: Implement setGps() method.
}
public function setSteering($type)
{
// TODO: Implement setSteering() method.
}
public function reset()
{
$this->car = new Car();
}
public function getCar() : Car
{
$this->reset();
return $this->car;
}
}
客户可以直接使用 CarBuilder 来分布构建对象
<?php
$builder = new CarBuilder();
$builder->setSeats(4);
$builder->setEngine("2.0T");
但是通常用专门的类来管理这个过程,我们称之为主管类。汽车主管类管理如何分步生成对象,并且,汽车主管类也可以创建多种类型的汽车。
<?php
class CarDirector
{
private $builder;
public setBuilder(Builder $builder)
{
$this->builder = builder;
}
public makeSuv()
{
$this->builder->setSeats(4);
$this->builder->setEngine("2.0T");
}
public function makeSmart()
{
}
}
客户端调用主管类即可
<?php
$director = new CarDirector();
$director->setBuilder(new CarBuilder());
$director->makeSuv();
从这里,我们可以看出,生成器的重点是「如何分步生成复杂对象」。
实现
产品不负责生成对象,只负责表示层
<?php
class Product1
{
public $parts = [];
public function listParts(): void
{
echo "产品构成: " . implode(', ', $this->parts) . "\n\n";
}
}
为产品的各个部件指定接口
<?php
interface Builder
{
public function productPartA() : void;
public function productPartB() : void;
public function productPartC() : void;
}
具体的产品生成器
<?php
class ConcreteBuilder1 implements Builder
{
private $product;
public function __construct()
{
$this->reset();
}
public function reset(): void
{
$this->product = new Product1;
}
public function producePartA(): void
{
$this->product->parts[] = "PartA1";
}
public function producePartB(): void
{
$this->product->parts[] = "PartB1";
}
public function producePartC(): void
{
$this->product->parts[] = "PartC1";
}
public function getProduct(): Product1
{
$result = $this->product;
$this->reset();
return $result;
}
}
主管类用于管理产品的生成步骤
<?php
class Director
{
private $builder;
public function setBuilder(Builder $builder): void
{
$this->builder = $builder;
}
public function buildMinimalViableProduct(): void
{
$this->builder->producePartA();
}
public function buildFullFeaturedProduct(): void
{
$this->builder->producePartA();
$this->builder->producePartB();
$this->builder->producePartC();
}
}
客户端可以使用主管类来管理对象的生成
<?php
function clientCode(Director $director)
{
$builder = new ConcreteBuilder1;
$director->setBuilder($builder);
$director->buildMinimalViableProduct();
$builder->getProduct()->listParts();
}
也可以直接使用生成器来创建对象
<?php
$builder->producePartA();
$builder->producePartC();
$builder->getProduct()->listParts();
应用
生成器的一个典型例子就是 SQL 查询生成器。一个 SQL 查询类包括了众多的语法组成部分,不同的数据库对应不同的实现,因此,可以用生成器来定义 SQL 查询的通用接口。具体实现由不同的数据库引擎实现。
定义 SQL 查询构建接口
<?php
interface SQLQueryBuilder
{
public function select(string $table, array $fields): SQLQueryBuilder;
public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder;
public function limit(int $start, int $offset): SQLQueryBuilder;
public function getSQL(): string;
}
MySQL 生成器
<?php
class MysqlQueryBuilder implements SQLQueryBuilder
{
protected $query;
protected function reset(): void
{
$this->query = new \stdClass;
}
public function select(string $table, array $fields): SQLQueryBuilder
{
$this->reset();
$this->query->base = "SELECT " . implode(", ", $fields) . " FROM " . $table;
$this->query->type = 'select';
return $this;
}
public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder
{
if (!in_array($this->query->type, ['select', 'update', 'delete'])) {
throw new \Exception("WHERE can only be added to SELECT, UPDATE OR DELETE");
}
$this->query->where[] = "$field $operator '$value'";
return $this;
}
public function limit(int $start, int $offset): SQLQueryBuilder
{
if (!in_array($this->query->type, ['select'])) {
throw new \Exception("LIMIT can only be added to SELECT");
}
$this->query->limit = " LIMIT " . $start . ", " . $offset;
return $this;
}
public function getSQL(): string
{
$query = $this->query;
$sql = $query->base;
if (!empty($query->where)) {
$sql .= " WHERE " . implode(' AND ', $query->where);
}
if (isset($query->limit)) {
$sql .= $query->limit;
}
$sql .= ";";
return $sql;
}
}
客户端
<?php
function clientCode(SQLQueryBuilder $queryBuilder)
{
$query = $queryBuilder
->select("users", ["name", "email", "password"])
->where("age", 18, ">")
->where("age", 30, "<")
->limit(10, 20)
->getSQL();
echo $query;
}
总结
当你的构造函数包括大量参数时,说明的对象过于复杂了,这时候可以考虑使用生成器来分步生成对象。同时,如果你的不同产品的实现过程相似,只是细节上的不同,那么可以用不同的生成器来创建特定的产品,并且,你可以使用主管类来管理生成的步骤。