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 {
public function getDashes(int $count) { $dash = ''; for ($i = 0; $i < $count; $i++) { $dash = $dash . '-'; }
return $dash; } }
|
DashHelper目的是做出不同層的分類。
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;
public $name;
protected $children = [];
public function __construct(string $name) { $this->name = $name; }
public function add(Component $component) { $this->children[$component->name] = $component; }
public function remove(Component $component) { unset($this->children[$component->name]); }
public function displayClassifiaction(int $depth) { $this->displaySelfClassification($depth); $this->displayChildrenClassification($depth); }
private function displaySelfClassification(int $depth) { $dashes = $this->getDashes($depth);
if (strlen($dashes) == 0) { echo "$this->name\n"; return; }
echo "$dashes $this->name\n"; }
private function displayChildrenClassification(int $depth) { foreach ($this->children as $child) { $child->displayClassifiaction($depth + 2); } } }
|
枝節點會先印出自己的分類名稱,接著加層數給子物件。
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;
public $name;
public function __construct(string $name) { $this->name = $name; }
public function add(Component $component) { throw new Exception('Cannot add to a leaf'); }
public function remove(Component $component) { throw new Exception('Cannot remove from a leaf'); }
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);
$diprotodontia = new Composite('雙門齒目'); $phascolarctidae = new Composite('無尾熊科'); $phascolarctos = new Composite('無尾熊屬'); $phascolarctosCinereus = new Leaf('無尾熊');
$diprotodontia->add($phascolarctidae); $phascolarctidae->add($phascolarctos); $phascolarctos->add($phascolarctosCinereus);
$mammalia->add($diprotodontia);
$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) 。
ʕ •ᴥ•ʔ:動物界-脊索動物門-哺乳綱-靈長目-人科-人屬-人。