範例:生物分類學(組合模式)

Pattern: 組合模式

Class Diagram: 生物分類學


情境:原本的生物分類學(界門綱目科屬種)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\CompositePattern\Taxonomy;

class Program
{
public function getTaxonomy()
{
echo '動物界
-- 脊索動物門
---- 哺乳綱
------ 雙門齒目
-------- 無尾熊科
---------- 無尾熊屬
------------ 無尾熊

------ 食肉目
-------- 熊科
---------- 大貓熊屬
------------ 大貓熊

';
}
}

(註:排版是因為測試時不能有空格,也間接說明了這是個脆弱測試)

我們利用「-」來做出層級的分類概念。

經由分類發現,無尾熊與大貓熊同屬動物界-脊索動物門-哺乳綱。

讓我們透過組合模式,將其改寫成樹形架構!


需求一:運用組合模式

  • 首先定義組合介面 (Component),採取透明模式 (uniformity)
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace App\CompositePattern\Taxonomy\Contracts;

interface Component
{
public function add(Component $component);

public function remove(Component $component);

public function displayClassifiaction(int $depth);
}


  • 定義DashHelper (重構時發現枝節點與葉節點可共用的方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace App\CompositePattern\Taxonomy\Traits;

trait DashHelper
{
/**
* @param integer $count
* @return string
*/
public function getDashes(int $count)
{
$dash = '';
for ($i = 0; $i < $count; $i++) {
$dash = $dash . '-';
}

return $dash;
}
}

DashHelper目的是做出不同層的分類。


  • 定義枝節點類別 (Composite)
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php

namespace App\CompositePattern\Taxonomy;

use App\CompositePattern\Taxonomy\Contracts\Component;
use App\CompositePattern\Taxonomy\Traits\DashHelper;

class Composite implements Component
{
use DashHelper;

/**
* @var string
*/
public $name;

/**
* @var Component[]
*/
protected $children = [];

/**
* @param string $name
*/
public function __construct(string $name)
{
$this->name = $name;
}

/**
* @param Component $component
* @return void
*/
public function add(Component $component)
{
$this->children[$component->name] = $component;
}

/**
* @param Component $component
* @return void
*/
public function remove(Component $component)
{
unset($this->children[$component->name]);
}

/**
* @param integer $depth
* @return void
*/
public function displayClassifiaction(int $depth)
{
$this->displaySelfClassification($depth);
$this->displayChildrenClassification($depth);
}

/**
* @param int $depth
* @return void
*/
private function displaySelfClassification(int $depth)
{
$dashes = $this->getDashes($depth);

if (strlen($dashes) == 0) {
echo "$this->name\n";
return;
}

echo "$dashes $this->name\n";
}

/**
* @param integer $depth
* @return void
*/
private function displayChildrenClassification(int $depth)
{
foreach ($this->children as $child) {
$child->displayClassifiaction($depth + 2);
}
}
}

枝節點會先印出自己的分類名稱,接著加層數給子物件。


  • 定義葉節點類別 (Leaf)
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
46
47
48
49
50
51
52
53
<?php

namespace App\CompositePattern\Taxonomy;

use App\CompositePattern\Taxonomy\Contracts\Component;
use App\CompositePattern\Taxonomy\Traits\DashHelper;
use Exception;

class Leaf implements Component
{
use DashHelper;

/**
* @var string
*/
public $name;

/**
* @param string $name
*/
public function __construct(string $name)
{
$this->name = $name;
}

/**
* @param Component $component
* @throws Exception
*/
public function add(Component $component)
{
throw new Exception('Cannot add to a leaf');
}

/**
* @param Component $component
* @throws Exception
*/
public function remove(Component $component)
{
throw new Exception('Cannot remove from a leaf');
}

/**
* @param integer $depth
* @return void
*/
public function displayClassifiaction(int $depth)
{
$dashes = $this->getDashes($depth);
echo "$dashes $this->name\n\n";
}
}

葉節點只會列出自己的分類名稱,而且不允許對子物件的操作。


  • 最後修改客戶端的程式碼
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
<?php

namespace App\CompositePattern\Taxonomy;

class Program
{
public function getTaxonomy()
{
$animalia = new Composite('動物界');
$chordata = new Composite('脊索動物門');
$mammalia = new Composite('哺乳綱');

$animalia->add($chordata);
$chordata->add($mammalia);

// koala
$diprotodontia = new Composite('雙門齒目');
$phascolarctidae = new Composite('無尾熊科');
$phascolarctos = new Composite('無尾熊屬');
$phascolarctosCinereus = new Leaf('無尾熊');

$diprotodontia->add($phascolarctidae);
$phascolarctidae->add($phascolarctos);
$phascolarctos->add($phascolarctosCinereus);

$mammalia->add($diprotodontia);

// panda
$carnivora = new Composite('食肉目');
$ursidae = new Composite('熊科');
$ailuropoda = new Composite('大貓熊屬');
$ailuropodaMelanoleuca = new Leaf('大貓熊');

$carnivora->add($ursidae);
$ursidae->add($ailuropoda);
$ailuropoda->add($ailuropodaMelanoleuca);

$mammalia->add($carnivora);

$animalia->displayClassifiaction(0);
}
}

(註:此處的變數命名參考學名)


[單一職責原則]
透過找出可以繼續遞迴的部分,分出枝節點與葉節點。

[開放封閉原則]
可以於組合中新增/修改某節點,不去影響其他節點的行為。

[依賴反轉原則]
客戶依賴於抽象的**組合介面 (Component)
枝節點與葉節點實現抽象的
組合介面 (Component)**。

ʕ •ᴥ•ʔ:動物界-脊索動物門-哺乳綱-靈長目-人科-人屬-人。