Skip to content
目录

常用面向对象设计原则

名称定义
单一职责原则(SRP)一个类只负责一个功能领域中的相应职责
开闭原则(OCP)对扩展开放,对修改关闭
里氏替换原则(LSP)所有引用基类对象的地方能够透明地使用其子类对象
依赖倒置原则(DIP)抽象不应该依赖于细节,而细节应该依赖于抽象
接口分离原则(ISP)使用多个专门接口,而不是单一的总接口
合成复用原则(CRP)尽量使用组成而不是继承
迪米特法则(LoD)一个软件实体应尽可能少地与其他实体发生相互作用

单一职责原则

单一职责原则:一个类或者模块只负责完成一个职责(或者功能)。

简单来说就是保证我们在设计函数、方法时做到功能单一,权责明确,当发生改变时,只有一个改变它的原因。如果函数/方法承担的功能过多,就意味着很多功能会相互耦合,这样当其中一个功能发生改变时,可能会影响其它功能。单一功能原则,可以使代码后期的维护成本更低、改动风险更低。

如一个统计商户门店交易报表的工具初始设计如下:

如上图,getConnection 方法用于连接数据库,findStoresfindOrders 分别获取商户下的门店和交易订单,calc 方法用于执行报表计算。StoreReport 类包含了获取数据和统计数据的相关方法,如果在其他类中也需要连接数据库、查询门店和订单的方法,则难以实现代码的重用。为此,对其进行拆分如下:

  1. DBUtil 负责连接数据库,包含连接数据库的方法:getConnection
  2. StoreDAO 负责操作数据库中 Store 表,包含获取门店的方法:findStores
  3. OrderDAO 负责操作数据库中 Order 表,包含获取订单的方法:findOrders
  4. StoreReport 负责报表数据的生成,包含方法 calc

开闭原则

对扩展开放、对修改关闭。

简单来说就是通过在已有代码基础上扩展代码,而非修改代码的方式来完成新功能的添加。开闭原则,并不是说完全杜绝修改,而是尽可能不修改或者以最小的代码修改代价来完成新功能的添加。

根据这一原则,一个软件实体能很容易地扩展新功能而不必修改现有的代码,如:

go
package book

// Book 书籍
type Book interface {
	Name() string
	ISBN() string
	Price() float64
}

// Novel 小说
type Novel struct {
	name  string
	isbn  string
	price float64
}

func (n *Novel) Name() string {
	return n.name
}

func (n *Novel) ISBN() string {
	return n.isbn
}

func (n *Novel) Price() float64 {
	return n.price
}

上述代码段,定义了一个 Book 接口和 Book 接口的一个实现:Novel(小说)。现在有新的需求,对所有小说打折统一打5折,根据开闭原则,打折相关的功能应该利用扩展实现,而不是在原有代码上修改。所以,新增一个 OffNovel,继承 Novel,并重写 Price 方法。

go
type OffNovel struct {
	Novel
}

func (n *OffNovel) Price() float64 {
	return n.Novel.price * 0.5
}

另一个关于报表数据图表显示的例子如下。现在支持饼图和直方图两种方式,常见写法是使用多个 if 判断不同的类型来呈现不同类型的图表,UML 如下:

这样如果以后增加其他类型图表则需要添加新的分支判断。优化后使用如下方式,则只需要新图表类型时只需要设置图表类型即可。

里氏替换原则

原指:程序里的对象都应该可以被它的子类实例替换而不用更改程序。在 GO 中没有类、对象和继承,可以使用接口来类比,如:

go
type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

type ReadWriter interface {
	Reader
	Writer
}

func Write(w Writer, p []byte) (int, error) {
	return w.Write(p)
}

我们可以将 Write 函数中的 Writer 参数替换为其子类型 ReadWriter,而不影响已有程序:

go
func Write(rw ReadWriter, p []byte) (int, error) {
	return rw.Write(p)
}

另一个关于发送邮件的例子如下。现有普通用户和VIP用户两种不同类型的用户,他们分别采用不同的方法发送邮件。

在进一步分析后发现,无论是普通客户还是VIP客户,发送邮件的过程都是相同的,也就是说两个send()方法中的代码重复,而且在增加新类型的客户后,还需要新增发送邮件的方法。为了让系统具有更好的扩展性,同时减少代码重复,使用里氏替换原则对其进行重构。

依赖倒置原则

抽象不应该依赖于细节,细节应当依赖于抽象。其本质是要面向接口编程,不要面向实现编程。

汽车制造公司:

go
package car

import "fmt"

// Car 汽车
type Car interface {
	Run()
}

// benz 奔驰汽车
type benz struct{}

func (b *benz) Run() {
	fmt.Println("奔驰汽车在疾驰……")
}

// bmw 宝马汽车
type bmw struct{}

func (B *bmw) Run() {
	fmt.Println("宝马汽车在疾驰……")
}

// NewBenz 生产一辆奔驰汽车
func NewBenz() Car {
	return &benz{}
}

// NewBMW 生产一辆宝马汽车
func NewBMW() Car {
	return &bmw{}
}

驾校,专门培训司机:

go
package driver

import (
	"code.lmcw.art/example/car"
)

// Driver 司机
type Driver interface {
	Drive(car car.Car)
}

type driver struct{}

func (d *driver) Drive(car car.Car) {
	car.Run()
}

// New 培训一名合格的司机
func New() Driver {
	return &driver{}
}

男女司机开汽车:

go
package main

import (
	"code.lmcw.art/example/car"
	"code.lmcw.art/example/driver"
)

func main() {
	benz := car.NewBenz() // 买一辆奔驰
	bmw := car.NewBMW() // 又买一辆宝马
	
	d := driver.New() // 培训一名司机
	d.Drive(benz) // 我一会儿开奔驰
	d.Drive(bmw) // 我一会儿开宝马
}

上述代码中,Driver 接口的 Drive 方法,依赖于 Car 接口,而不是某一具体的汽车类型。这样司机就可以开不同的汽车。

接口隔离原则

其本质目的是表达:多个专用的接口比一个通用接口好 。即一个类决不要实现不会用到的接口,实现多个特定的接口比实现一个通用接口要好。

例如,我们通过 Save 函数将文档保存到磁盘:

go
// Save writes the contents of doc to the file f.
func Save(f *os.File, doc *Document) error

上面的 Save 函数虽然能够完成任务,但有下面的问题:

  1. Save 函数使用 *os.File 做为保存 Document 的文件,如果后面需要将 Document 保存到网络存储,Save 函数就无法满足。
  2. 因为 Save 函数直接保存文档到磁盘,不方便后续的测试。
  3. *os.File 包含了许多跟 Save 函数的方法,例如:读取路径以及检查路径是否是软连接等。

可以说上述的 Save 方法不满足接口隔离原则。可以优化为:

go
// Save writes the contents of doc to the supplied ReadWriter.
func Save(rw io.ReadWriter, doc *Document) error

但是上述的 Save 函数仍然有问题,我们其实只需要写操作,不需要读操作,因此,Save 函数可以进一步优化为:

go
// Save writes the contents of doc to the supplied Writer.
func Save(w io.Writer, doc *Document) error

func Save(w io.Writer, doc *Document) error就是最终的符合接口隔离原则的函数。

合成复用原则

尽量使用对象组合,而不是继承来达到复用的目的。参考单一职责原则示例。

迪米特法则

如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。

创建型设计模式

提供创建对象的机制,包括工厂方法、抽象工厂、生成器、原型和单例。

工厂方法

在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

假设我们现在正在做一个自媒体工具,用于将自己创建的视频同步发布到抖音和快手平台。视频采用如下方式存储:

php
<?php

class Podcast
{
    public function __construct(
        public readonly string $title,
        public readonly string $content,
    ) {
    }
}

接下来,需要先定义两个抽象的工具类分别用来处理自媒体平台的连接等操作及核心的播客投递,我们分别叫它WeMediaConnectorPodcastPoster

php
/**
 * 假定每个自媒体平台都只需要做登录、发表、退出的操作
 */
interface WeMediaConnector
{
    public function login(): void;

    public function publish(Podcast $podcast): void;

    public function logout(): void;
}

/**
 * 播客投递器,用于将写好的播客发表到指定的自媒体平台
 */
abstract class PodcastPoster
{
    abstract public function getConnector(): WeMediaConnector;

    public function post(Podcast $podcast): void
    {
        $connector = $this->getConnector();
        $connector->login();
        $connector->publish($podcast);
        $connector->logout();
    }
}

接下来我们分别实现抖音和快手平台的 ConnectorPoster

php
<?php
    
/**
 * 抖音平台连接器
 */
class DouyinConnector implements WeMediaConnector
{
    public function __construct(
        private readonly string $username,
        private readonly string $password,
    ) {
    }

    public function login(): void
    {
        echo "Login to Douyin as $this->username with password $this->password\n";
    }

    public function publish(Podcast $podcast): void
    {
        echo "Publish podcast to Douyin: $podcast->title, $podcast->content\n";
    }

    public function logout(): void
    {
        echo "Logout from Douyin\n";
    }
}

抖音平台投递器

php
<?php

class DouyinPoster extends PodcastPoster
{
    public function __construct(
        private readonly string $username,
        private readonly string $password,
    ) {
    }

    public function getConnector(): WeMediaConnector
    {
        return new DouyinConnector($this->username, $this->password);
    }
}

快手平台连接器

php
<?php

class KuaishouConnector implements WeMediaConnector
{
    public function __construct(
        private readonly string $username,
        private readonly string $password,
    ) {
    }

    public function login(): void
    {
        echo "Login to Kuaishou as $this->username with password $this->password\n";
    }

    public function publish(Podcast $podcast): void
    {
        echo "Publish podcast to Kuaishou: $podcast->title, $podcast->content\n";
    }

    public function logout(): void
    {
        echo "Logout from Kuaishou\n";
    }
}

快手平台投递器

php
class KuaishouPoster extends PodcastPoster
{
    public function __construct(
        private readonly string $username,
        private readonly string $password,
    ) {
    }

    public function getConnector(): WeMediaConnector
    {
        return new KuaishouConnector($this->username, $this->password);
    }
}

接下来我们就开始创作播客及投递了。

php
/**
 * 播客发布代理
 */
function broker(
    WeMediaPlatform $platform,
    string $username,
    string $password,
    Podcast $podcast,
): void {
    $poster = match ($platform) {
        WeMediaPlatform::DOUYIN =>
        new DouyinPoster($username, $password),
        WeMediaPlatform::KUAISHOU =>
        new KuaishouPoster($username, $password),
    };

    $poster->post($podcast);
}

// 创作播客
$podcast = new Podcast('title', 'content');

// 将播客通过代理发布到抖音和快手
broker(
    platform: WeMediaPlatform::DOUYIN,
    username: 'douyin-username',
    password: 'douyin-password',
    podcast: $podcast,
);
broker(
    platform: WeMediaPlatform::KUAISHOU,
    username: 'kuaishou-username',
    password: 'kuaishou-password',
    podcast: $podcast,
);

抽象工厂

如果说工厂方法是用来创建单个产品的话,那抽象工厂则是用于创建一系列相关产品。现在假设我们现在需要对接微信、支付宝、银联三个支付渠道,而每个支付渠道都有支付收银、异步通知、对账单三种功能。其对应的实现如下。

首先定义 支付渠道 工厂及三个产品 支付收银异步通知对账单

php
<?php

interface PayChannel
{
    public function getCashier(): Cashier;

    public function getBillDownloader(): BillDownloader;

    public function getAsyncNotifyHandler(): AsyncNotifyHandler;
}

interface Cashier
{
    public function tradePay();

    public function tradeQuery();

    public function tradeRefund();

    public function refundQuery();
}

interface AsyncNotifyHandler
{
    public function handleNotify();
}

interface BillDownloader
{
    public function downloadBill();
}

接下来分别实现微信、支付宝、银联三个支付渠道及与之对应的产品功能,首先是微信。

php
<?php

class WechatPay implements PayChannel
{
    public function __construct(private readonly array $credentials)
    {
    }

    public function getCashier(): Cashier
    {
        return new WechatCashier($this->credentials);
    }

    public function getBillDownloader(): BillDownloader
    {
        return new WechatBillDownloader($this->credentials);
    }

    public function getAsyncNotifyHandler(): AsyncNotifyHandler
    {
        return new WechatAsyncNotifyHandler($this->credentials);
    }
}

class WechatCashier implements Cashier
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化微信收银';
    }

    public function tradePay(): void
    {
        echo '微信支付';
    }

    public function tradeQuery(): void
    {
        echo '微信查询';
    }

    public function tradeRefund(): void
    {
        echo '微信退款';
    }

    public function refundQuery(): void
    {
        echo '微信退款查询';
    }
}

class WechatBillDownloader implements BillDownloader
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化微信账单下载';
    }

    public function downloadBill(): void
    {
        echo '微信账单下载';
    }
}

class WechatAsyncNotifyHandler implements AsyncNotifyHandler
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化微信异步通知处理器';
    }

    public function handleNotify(): void
    {
        echo '微信异步通知处理';
    }
}

下面是支付宝:

php
<?php
    
class Alipay implements PayChannel
{
    public function __construct(private readonly array $credentials)
    {
    }

    public function getCashier(): Cashier
    {
        return new AlipayCashier($this->credentials);
    }

    public function getBillDownloader(): BillDownloader
    {
        return new AlipayBillDownloader($this->credentials);
    }

    public function getAsyncNotifyHandler(): AsyncNotifyHandler
    {
        return new AlipayAsyncNotifyHandler($this->credentials);
    }
}

class AlipayCashier implements Cashier
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化支付宝收银';
    }

    public function tradePay(): void
    {
        echo '支付宝支付';
    }

    public function tradeQuery(): void
    {
        echo '支付宝查询';
    }

    public function tradeRefund(): void
    {
        echo '支付宝退款';
    }

    public function refundQuery(): void
    {
        echo '支付宝退款查询';
    }
}

class AlipayBillDownloader implements BillDownloader
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化支付宝账单下载器';
    }

    public function downloadBill(): void
    {
        echo '支付宝账单下载';
    }
}

class AlipayAsyncNotifyHandler implements AsyncNotifyHandler
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化支付宝异步通知处理器';
    }

    public function handleNotify(): void
    {
        echo '支付宝异步通知处理';
    }
}

然后是银联:

php
<?php
    
class Unionpay implements PayChannel
{
    public function __construct(private readonly array $credentials)
    {
    }

    public function getCashier(): Cashier
    {
        return new UnionpayCashier($this->credentials);
    }

    public function getBillDownloader(): BillDownloader
    {
        return new UnionpayBillDownloader($this->credentials);
    }

    public function getAsyncNotifyHandler(): AsyncNotifyHandler
    {
        return new UnionpayAsyncNotifyHandler($this->credentials);
    }
}

class UnionpayCashier implements Cashier
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化银联支付收银';
    }

    public function tradePay(): void
    {
        echo '银联支付';
    }

    public function tradeQuery(): void
    {
        echo '银联查询';
    }

    public function tradeRefund(): void
    {
        echo '银联退款';
    }

    public function refundQuery(): void
    {
        echo '银联退款查询';
    }
}

class UnionpayBillDownloader implements BillDownloader
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化银联支付账单下载器';
    }

    public function downloadBill(): void
    {
        echo '银联账单下载';
    }
}

class UnionpayAsyncNotifyHandler implements AsyncNotifyHandler
{
    public function __construct(private readonly array $credentials)
    {
        echo '使用' . json_encode($this->credentials) . '初始化银联支付异步通知处理器';
    }

    public function handleNotify(): void
    {
        echo '银联异步通知处理';
    }
}

最后我们看看他们的使用方法:

php
<?php
    
enum PayChannelType: string
{
    case WECHAT = 'wechat';
    case ALIPAY = 'alipay';
    case UNIONPAY = 'unionpay';
}

$credentials = [];

function channelBroker(PayChannelType $type, array $credentials): PayChannel
{
    return match ($type) {
        PayChannelType::WECHAT => new WechatPay($credentials),
        PayChannelType::ALIPAY => new Alipay($credentials),
        PayChannelType::UNIONPAY => new Unionpay($credentials),
    };
}

$channel = channelBroker(PayChannelType::WECHAT, $credentials);
$channel->getCashier()->tradePay();

单例模式

实际开发中,为了节约系统资源,需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,我们可以通过单例模式来实现。我们基于上一个例子,对每个 PayChannel 的实现使用单例模式来实例化。以 WechatWechatPay 为例。

php
<?php
    
class WechatPay implements PayChannel
{
    private static array $instances;

    private function __construct(private readonly array $credentials)
    {
    }

    private function __clone() {}

    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize singleton");
    }

    public static function getInstance(array $credentials): self
    {
        $class = static::class;
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new self($credentials);
        }

        return self::$instances[$class];
    }

    ...
}

使用方式如下:

php
<?php
    
function channelBroker(PayChannelType $type, array $credentials): PayChannel
{
    return match ($type) {
        PayChannelType::WECHAT => WechatPay::getInstance($credentials),
        PayChannelType::ALIPAY => Alipay::getInstance($credentials),
        PayChannelType::UNIONPAY => Unionpay::getInstance($credentials),
    };
}

原型

原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中应用较为广泛,很多软件提供的复制和粘贴操作就是原型模式的典型应用。

当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。

考虑我们有一个笔记软件,现在需要支持对笔记的拷贝功能,以快速新建一个有内容的笔记。

php
class Note
{
    private string $title;

    private string $content;

    private Author $author;

    private array $comments = [];

    private DateTime $createdAt;

    public function __construct(string $title, string $content, Author $author)
    {
        $this->title = $title;
        $this->content = $content;
        $this->author = $author;
        $this->createdAt = new DateTime();
        $this->author->addToNote($this);
    }

    public function addComment(string $comment): void
    {
        $this->comments[] = $comment;
    }

    public function __clone()
    {
        $this->title = 'Copy of ' . $this->title;
        $this->author->addToNote($this);
        $this->comments = [];
        $this->createdAt = new DateTime();
    }
}

class Author
{
    /**
     * @var Note[]
     */
    private array $notes = [];

    public function __construct(private readonly string $name)
    {
    }

    public function addToNote(Note $note): void
    {
        $this->notes[] = $note;
    }
}

使用方法:

php
// 创建第一篇笔记
$author = new Author('Tom');
$note = new Note('My first note', 'This is my first note', $author);
$note->addComment('This is my first comment');

// 拷贝第一篇笔记以作为草稿
$draft = clone $note;
echo "注意,作者现在引用了两篇笔记。\n\n";
print_r($draft);

生成器

生成器是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。

假设你现在经营着一家汽车制造公司,你为公司聘请了一位主管及两位员工,主管负责指挥员工生产汽车及编制汽车使用说明书。以下是一些基础件。

php
<?php

// 公司可以生产的汽车类型
enum CarType: string
{
    case CITY_CAR = 'city_car';
    case SUV = 'suv';
    case SPORTS_CAR = 'sports_car';
}

// 手动挡、自动挡、半自动等
enum Transmission: string
{
    case MANUAL = 'manual';

    case AUTOMATIC = 'automatic';

    case SINGLE_SPEED = 'single_speed';

    case SEMI_AUTOMATIC = 'semi_automatic';
}

// 汽车引擎
class Engine
{
    private bool $started = false;

    public function __construct(
        private readonly float $volume,
        private float $mileage,
    ) {
    }

    public function on()
    {
        $this->started = true;
    }

    public function off()
    {
        $this->started = false;
    }

    public function isStarted()
    {
        return $this->started;
    }

    public function go(float $mileage)
    {
        if ($this->started) {
            $this->mileage += $mileage;
        } else {
            throw new RuntimeException('Cannot go(), you must start engine first!');
        }
    }

    public function getVolume()
    {
        return $this->volume;
    }

    public function getMileage()
    {
        return $this->mileage;
    }
}

// 燃油指示器
class TripComputer
{
    private Car $car;

    public function setCar(Car $car)
    {
        $this->car = $car;
    }

    public function showFuelLevel()
    {
        echo "Fuel level: " . $this->car->getFuel() . "\n";
    }

    public function showStatus()
    {
        if ($this->car->getEngine()->isStarted()) {
            echo "Engine started\n";
        } else {
            echo "Engine stopped\n";
        }
    }
}

// GPS导航
class GPSNavigator
{
    private string $route;

    public function __construct(string $manualRoute = '')
    {
        $this->route = $manualRoute;
    }

    public function getRoute(): string
    {
        return $this->route;
    }
}

// 汽车
class Car
{
    private float $fuel = 0.0;

    public function __construct(
        private readonly CarType $type,
        private readonly int $seats,
        private readonly Engine $engine,
        private readonly Transmission $transmission,
        private readonly GPSNavigator $gpsNavigator,
        private readonly ?TripComputer $tripComputer = null,
    ) {
        $this->tripComputer?->setCar($this);
    }

    public function getFuel(): float
    {
        return $this->fuel;
    }

    public function setFuel(float $fuel): void
    {
        $this->fuel = $fuel;
    }

    public function getType(): CarType
    {
        return $this->type;
    }

    public function getSeats(): int
    {
        return $this->seats;
    }

    public function getEngine(): Engine
    {
        return $this->engine;
    }

    public function getTransmission(): Transmission
    {
        return $this->transmission;
    }

    public function getGPSNavigator(): GPSNavigator
    {
        return $this->gpsNavigator;
    }

    public function getTripComputer(): ?TripComputer
    {
        return $this->tripComputer;
    }
}

// 汽车使用手册
class Manual
{
    public function __construct(
        private readonly CarType $type,
        private readonly int $seats,
        private readonly Engine $engine,
        private readonly Transmission $transmission,
        private readonly ?GPSNavigator $gpsNavigator = null,
        private readonly ?TripComputer $tripComputer = null,
    ) {
    }

    public function content(): void
    {
        echo sprintf(
            "Type of car: %s\nCount of seats: %d\nEngine: volume - %s, mileage - %.2f\n%s\n%s\n%s\n",
            $this->type->value,
            $this->seats,
            $this->engine->getVolume(),
            $this->engine->getMileage(),
            $this->transmission->value,
            $this->gpsNavigator?->getRoute() ?? 'No GPS Navigator',
            $this->tripComputer?->showFuelLevel() ?? 'No Trip Computer',
        );
    }
}

接下来我们定义汽车及汽车使用手册生成器,即两名工人。

php
<?php
    
interface Builder
{
    public function setCarType(CarType $carType): void;

    public function setSeats(int $seats): void;

    public function setEngine(Engine $engine): void;

    public function setTransmission(Transmission $transmission): void;

    public function setTripComputer(TripComputer $tripComputer): void;

    public function setGPSNavigator(GPSNavigator $gpsNavigator): void;
}

class CardBuilder implements Builder
{
    private CarType $type;

    private int $seats;

    private Engine $engine;

    private Transmission $transmission;

    private TripComputer $tripComputer;

    private GPSNavigator $gpsNavigator;

    public function setCarType(CarType $carType): void
    {
        $this->type = $carType;
    }

    public function setSeats(int $seats): void
    {
        $this->seats = $seats;
    }

    public function setEngine(Engine $engine): void
    {
        $this->engine = $engine;
    }

    public function setTransmission(Transmission $transmission): void
    {
        $this->transmission = $transmission;
    }

    public function setTripComputer(TripComputer $tripComputer): void
    {
        $this->tripComputer = $tripComputer;
    }

    public function setGPSNavigator(GPSNavigator $gpsNavigator): void
    {
        $this->gpsNavigator = $gpsNavigator;
    }

    public function getResult(): Car
    {
        return new Car(
            $this->type,
            $this->seats,
            $this->engine,
            $this->transmission,
            $this->gpsNavigator,
            $this->tripComputer,
        );
    }
}

class CarManualBuilder implements Builder
{
    private CarType $type;

    private int $seats;

    private Engine $engine;

    private Transmission $transmission;

    private TripComputer $tripComputer;

    private GPSNavigator $gpsNavigator;

    public function setCarType(CarType $carType): void
    {
        $this->type = $carType;
    }

    public function setSeats(int $seats): void
    {
        $this->seats = $seats;
    }

    public function setEngine(Engine $engine): void
    {
        $this->engine = $engine;
    }

    public function setTransmission(Transmission $transmission): void
    {
        $this->transmission = $transmission;
    }

    public function setTripComputer(TripComputer $tripComputer): void
    {
        $this->tripComputer = $tripComputer;
    }

    public function setGPSNavigator(GPSNavigator $gpsNavigator): void
    {
        $this->gpsNavigator = $gpsNavigator;
    }


    public function getResult(): Manual
    {
        return new Manual(
            $this->type,
            $this->seats,
            $this->engine,
            $this->transmission,
            $this->gpsNavigator,
            $this->tripComputer,
        );
    }
}

然后是主管,他负责分配工人干活。

php
<?php

class Director
{
    public function constructSportsCar(Builder $builder): void
    {
        $builder->setCarType(CarType::SPORTS_CAR);
        $builder->setSeats(2);
        $builder->setEngine(new Engine(3.0, 0));
        $builder->setTransmission(Transmission::SEMI_AUTOMATIC);
        $builder->setTripComputer(new TripComputer());
        $builder->setGPSNavigator(new GPSNavigator('1234567890'));
    }

    public function constructCityCar(Builder $builder)
    {
        $builder->setCarType(CarType::CITY_CAR);
        $builder->setSeats(5);
        $builder->setEngine(new Engine(1.2, 0));
        $builder->setTransmission(Transmission::AUTOMATIC);
        $builder->setTripComputer(new TripComputer());
        $builder->setGPSNavigator(new GPSNavigator());
    }

    public function constructSUV(Builder $builder)
    {
        $builder->setCarType(CarType::SUV);
        $builder->setSeats(7);
        $builder->setEngine(new Engine(2.5, 0));
        $builder->setTransmission(Transmission::MANUAL);
        $builder->setGPSNavigator(new GPSNavigator());
    }
}

最后是实际工作的实施过程,即生成器使用方法。

php
<?php

// 聘请主管
$director = new Director();
// 招募汽车制造工人
$builder = new CardBuilder();
// 招募汽车使用手册编撰工人
$manualBuilder = new CarManualBuilder();

// 生产汽车
$director->constructSportsCar($builder);
$sportsCar = $builder->getResult();

// 编制使用手册
$director->constructSportsCar($manualBuilder);
$manual = $manualBuilder->getResult();

结构型模式