生成器

未匹配的标注

意图

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

生成器针对是「复杂的对象」,例如,一辆汽车包括了座位、引擎、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;
}

总结

当你的构造函数包括大量参数时,说明的对象过于复杂了,这时候可以考虑使用生成器来分步生成对象。同时,如果你的不同产品的实现过程相似,只是细节上的不同,那么可以用不同的生成器来创建特定的产品,并且,你可以使用主管类来管理生成的步骤。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~