範例:三隻小豬 (原型模式)

Pattern: 原型模式

Class Diagram: 三隻小豬


情境:三隻小豬想要蓋房子

  • 首先是牆壁,可以選擇不同的建材
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App\PrototypePattern\Building;

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

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

  • 接著是房子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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) ,蓋了間稻草屋
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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) 來蓋房子。

  • 決定拷貝稻草屋的弟弟們
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?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()方法,讓拷貝時能重新創建牆壁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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;
}
}
  • 不用修改原本的程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?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) 的不同。


後來三隻小豬跑去開建設公司,又是另一個故事了。

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