本文同步更新於blog
情境:原本的生物分類學(界門綱目科屬種)
<?php
namespace App\CompositePattern\Taxonomy;
class Program
{
public function getTaxonomy()
{
echo '動物界
-- 脊索動物門
---- 哺乳綱
------ 雙門齒目
-------- 無尾熊科
---------- 無尾熊屬
------------ 無尾熊
------ 食肉目
-------- 熊科
---------- 大貓熊屬
------------ 大貓熊
';
}
}
(註:排版是因為測試時不能有空格,也間接說明了這是個脆弱測試)
我們利用「-」來做出層級的分類概念。
經由分類發現,無尾熊與大貓熊同屬動物界-脊索動物門-哺乳綱。
讓我們透過組合模式,將其改寫成樹形架構!
需求一:運用組合模式
<?php
namespace App\CompositePattern\Taxonomy\Contracts;
interface Component
{
public function add(Component $component);
public function remove(Component $component);
public function displayClassifiaction(int $depth);
}
<?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目的是做出不同層的分類。
<?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);
}
}
}
枝節點會先印出自己的分類名稱,接著加層數給子物件。
<?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";
}
}
葉節點只會列出自己的分類名稱,而且不允許對子物件的操作。
<?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)。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:動物界-脊索動物門-哺乳綱-靈長目-人科-人屬-人。