Day38. 范例:三只小猪 (原型模式)

本文同步更新於blog

情境:三只小猪想要盖房子

  • 首先是墙壁,可以选择不同的建材
<?php

namespace App\PrototypePattern\Building;

class Wall
{
    /**
     * @var string
     */
    public $material;

    public function __construct(string $material)
    {
        $this->material = $material;
    }
}

  • 接着是房子
<?php

namespace App\PrototypePattern\Building;

use App\PrototypePattern\Building\Wall;

class Building
{
    /**
     * @var string
     */
    public $name;

    /**
     * @var Wall
     */
    public $wall;

    /**
     * @param Wall $wall
     * @param string $name
     */
    public function __construct(Wall $wall, string $name = 'unnamed')
    {
        $this->wall = $wall;
        $this->name = $name;
    }
}

  • 接着猪大哥用稻草 (straw),盖了间稻草屋
<?php

namespace App\PrototypePattern\Building;

use App\PrototypePattern\Building\Building;
use App\PrototypePattern\Building\Wall;

class Program
{
    /**
     * @return array
     */
    public function run()
    {
        //firstBuilding
        $strawWall = new Wall('straw');
        $firstBuilding = new Building($strawWall, 'oldestPigHouse');

        dump($firstBuilding->name); // oldestPigHouse
        dump($firstBuilding->wall->material); // straw
    }
}

完成後,猪大哥很得意自己对房子的设计,
询问弟弟们要不要直接拷贝一间。


需求一:拷贝猪大哥的稻草屋

两个弟弟想了想,拷贝大哥的房子,好像是个省事的方法,
但二弟想用木材 (wood),三弟想用砖块 (bricks) 来盖房子。

  • 决定拷贝稻草屋的弟弟们
<?php

namespace App\PrototypePattern\Building;

use App\PrototypePattern\Building\Building;
use App\PrototypePattern\Building\Wall;

class Program
{
    /**
     * @return array
     */
    public function run()
    {
        //firstBuilding
        $strawWall = new Wall('straw');
        $firstBuilding = new Building($strawWall, 'oldestPigHouse');

        //secondBuilding
        $secondBuilding = clone $firstBuilding;
        $secondBuilding->name = 'middlePigHouse';
        $secondBuilding->wall->material = 'wood';

        //thirdBuilding
        $thirdBuilding = clone $firstBuilding;
        $thirdBuilding->name = 'youngestPigHouse';
        $thirdBuilding->wall->material = 'bricks';

        dump($firstBuilding->name); // oldestPigHouse
        dump($firstBuilding->wall->material); // bricks

        dump($secondBuilding->name); // middlePigHouse
        dump($secondBuilding->wall->material); // bricks

        dump($thirdBuilding->name); // youngestPigHouse
        dump($thirdBuilding->wall->material); // bricks

        return [
            'firstBuilding' => $firstBuilding,
            'secondBuilding' => $secondBuilding,
            'thirdBuilding' => $thirdBuilding
        ];
    }
}

这时发生了件很尴尬的事情,
当猪二哥选用木材当建材时,大哥的稻草屋就变成木头屋了...
而猪小弟选用砖块当建材时,两个哥哥们的房子就变成砖头屋了...

导演表示,这样故事没办法进行下去,请我们修改一下。


需求二:让弟弟们对建材的修改,不会影响到哥哥

  • 修改Building中的clone()方法,让拷贝时能重新创建墙壁
<?php

namespace App\PrototypePattern\Building;

use App\PrototypePattern\Building\Wall;

class Building
{
    /**
     * @var string
     */
    public $name;

    /**
     * @var Wall
     */
    public $wall;

    /**
     * @param Wall $wall
     * @param string $name
     */
    public function __construct(Wall $wall, string $name = 'unnamed')
    {
        $this->wall = $wall;
        $this->name = $name;
    }

    public function __clone()
    {
        $this->wall = clone $this->wall;
    }
}
  • 不用修改原本的程序码
<?php

namespace App\PrototypePattern\Building;

use App\PrototypePattern\Building\Building;
use App\PrototypePattern\Building\Wall;

class Program
{
    /**
     * @return array
     */
    public function run()
    {
        //firstBuilding
        $strawWall = new Wall('straw');
        $firstBuilding = new Building($strawWall, 'oldestPigHouse');

        //secondBuilding
        $secondBuilding = clone $firstBuilding;
        $secondBuilding->name = 'middlePigHouse';
        $secondBuilding->wall->material = 'wood';

        //thirdBuilding
        $thirdBuilding = clone $firstBuilding;
        $thirdBuilding->name = 'youngestPigHouse';
        $thirdBuilding->wall->material = 'bricks';

        dump($firstBuilding->name); // oldestPigHouse
        dump($firstBuilding->wall->material); // straw

        dump($secondBuilding->name); // middlePigHouse
        dump($secondBuilding->wall->material); // wood

        dump($thirdBuilding->name); // youngestPigHouse
        dump($thirdBuilding->wall->material); // bricks

        return [
            'firstBuilding' => $firstBuilding,
            'secondBuilding' => $secondBuilding,
            'thirdBuilding' => $thirdBuilding
        ];
    }
}

後来弟弟们改用其他建材时,就不会影响到原本哥哥的房子了。

这就是浅复制 (Shallow Copy)深复制 (Deep Copy) 的不同。

後来三只小猪跑去开建设公司,又是另一个故事了。


最後附上类别图:
https://ithelp.ithome.com.tw/upload/images/20201031/20111630Uabzi4dLRG.png
(注:若不熟悉 UML 类别图,可参考UML类别图说明。)

ʕ •ᴥ•ʔ:一开始clone()方法能直接使用,是因为PHP的魔术方法__clone()唷!


<<:  第 56 天 - 帐号管理 - 批量查询使用者资料

>>:  (33)试着学 Hexo-番外篇之更新 NexT 主题

自动化测试,让你上班拥有一杯咖啡的时间 | Day 20 - invoke 的用法

此系列文章会同步发文到个人部落格,有兴趣的读者可以前往观看喔。 在浏览器上操作网页时,常常会开启新...

Leetcode 挑战 Day 15 [27. Remove Element]

27. Remove Element 今天我们一起挑战leetcode第27题Remove Elem...

Flutter体验 Day 16-滚动组件-Sliver

滚动组件-Sliver 若想要自定义滚动效果的介面功能,就需要使用 CustomScrollView...

ASP.NET MVC 从入门到放弃(Day11) -C# 连线资料库介绍( ADO.NET )

接着来讲讲资料库连线的部分.... Mysql 类别Class public class Categ...

Python 演算法 Day 13 - Ensemble Learning

Chap.II Machine Learning 机器学习 https://yourfreetemp...