文章来源refactoringguru.cn/design-patt…
PHP 生成器模式讲解和代码示例
生成器是一种创建型设计模式, 使你能够分步骤创建复杂对象。
与其他创建型模式不同, 生成器不要求产品拥有通用接口。 这使得用相同的创建过程生成不同的产品成为可能。
复杂度:******
流行度:******
使用示例: 生成器模式是 PHP 世界中的一个著名模式。 当你需要创建一个可能有许多配置选项的对象时, 该模式会特别有用。
识别方法: 生成器模式可以通过类来识别, 它拥有一个构建方法和多个配置结果对象的方法。 生成器方法通常支持方法链 (例如 someBuilder->setValueA(1)->setValueB(2)->create())。
真实世界示例
生成器模式最好的应用方式之一是 SQL 查询生成器。 生成器接口定义了生成一般 SQL 查询所需的通用步骤。 另一方面, 对应不同 SQL 语言的具体生成器会去实现这些步骤, 返回能在特定数据库引擎中执行的 SQL 查询语句。
** index.php: 真实世界示例
<?php
namespace RefactoringGuru\Builder\RealWorld;
/**
* The Builder interface declares a set of methods to assemble an SQL query.
*
* All of the construction steps are returning the current builder object to
* allow chaining: $builder->select(...)->where(...)
*/
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;
// +100 other SQL syntax methods...
public function getSQL(): string;
}
/**
* Each Concrete Builder corresponds to a specific SQL dialect and may implement
* the builder steps a little bit differently from the others.
*
* This Concrete Builder can build SQL queries compatible with MySQL.
*/
class MysqlQueryBuilder implements SQLQueryBuilder
{
protected $query;
protected function reset(): void
{
$this->query = new \stdClass();
}
/**
* Build a base SELECT query.
*/
public function select(string $table, array $fields): SQLQueryBuilder
{
$this->reset();
$this->query->base = "SELECT " . implode(", ", $fields) . " FROM " . $table;
$this->query->type = 'select';
return $this;
}
/**
* Add a WHERE condition.
*/
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;
}
/**
* Add a LIMIT constraint.
*/
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;
}
/**
* Get the final query string.
*/
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;
}
}
/**
* This Concrete Builder is compatible with PostgreSQL. While Postgres is very
* similar to Mysql, it still has several differences. To reuse the common code,
* we extend it from the MySQL builder, while overriding some of the building
* steps.
*/
class PostgresQueryBuilder extends MysqlQueryBuilder
{
/**
* Among other things, PostgreSQL has slightly different LIMIT syntax.
*/
public function limit(int $start, int $offset): SQLQueryBuilder
{
parent::limit($start, $offset);
$this->query->limit = " LIMIT " . $start . " OFFSET " . $offset;
return $this;
}
// + tons of other overrides...
}
/**
* Note that the client code uses the builder object directly. A designated
* Director class is not necessary in this case, because the client code needs
* different queries almost every time, so the sequence of the construction
* steps cannot be easily reused.
*
* Since all our query builders create products of the same type (which is a
* string), we can interact with all builders using their common interface.
* Later, if we implement a new Builder class, we will be able to pass its
* instance to the existing client code without breaking it thanks to the
* SQLQueryBuilder interface.
*/
function clientCode(SQLQueryBuilder $queryBuilder)
{
// ...
$query = $queryBuilder
->select("users", ["name", "email", "password"])
->where("age", 18, ">")
->where("age", 30, "<")
->limit(10, 20)
->getSQL();
echo $query;
// ...
}
/**
* The application selects the proper query builder type depending on a current
* configuration or the environment settings.
*/
// if ($_ENV['database_type'] == 'postgres') {
// $builder = new PostgresQueryBuilder(); } else {
// $builder = new MysqlQueryBuilder(); }
//
// clientCode($builder);
echo "Testing MySQL query builder:\n";
clientCode(new MysqlQueryBuilder());
echo "\n\n";
echo "Testing PostgresSQL query builder:\n";
clientCode(new PostgresQueryBuilder());
** Output.txt: 执行结果
Testing MySQL query builder:
SELECT name, email, password FROM users WHERE age > '18' AND age < '30' LIMIT 10, 20;
Testing PostgresSQL query builder:
SELECT name, email, password FROM users WHERE age > '18' AND age < '30' LIMIT 10 OFF