【PHPデザインパターン】12_Composite~木構造を表す


引用記事

この記事を書くきっかけになったブログです。

記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。

Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Composite~木構造を表す

概要

  • 「composite」とは「合成物」「混合物」という意味
  • 部分〜全体階層を表現するために、オブジェクトを木構造に組み立てる。
  • それにより、単体のオブジェクトとオブジェクトの集合を同一視することができる。
  • 単一のオブジェクトにも、複数のオブジェクトから形成されたオブジェクトにも、同じ手順でアクセスできるAPIを提供する。

構成要素

Componentクラス

  • 直訳すると「成分」「構成要素」。
  • Clientクラスに対して共通にアクセスさせるためのAPIを提供するクラス。
  • 子に相当するオブジェクトにアクセスしたり、追加・削除するためのAPIも含む。

Leafクラス

  • Componentクラスのサブクラス
  • 木構造の末端に位置する葉に相当するクラス。
  • 子に相当するオブジェクトを持たない。

Compositeクラス

  • 直訳すると「複合」。
  • Componentクラスのサブクラス。
  • 木構造の中で任意枝に相当するクラス。
  • 子に相当するオブジェクトを持つ。
  • 子に相当するオブジェクトにアクセスしたり、追加・削除するためのAPIを実装する。

Clientクラス

  • ComponentクラスのAPIを通して、木構造にアクセスする。

実演

処理の流れ

  • Entryクラスでクライアントからアクセスできる共通のAPIを定義する。
  • Itemクラスを枝、Foodクラスを葉に見立て、メソッドを実装する。
  • 階層が多重構造の場合も、再帰処理を行い各要素(Entryクラスのオブジェクト)に対してメソッドを実行する。

ファイル構造

MyComposite
  ├── Entry.php
  ├── Food.php
  ├── Item.php
  └── my_client.php

ソースコード

Componentクラス

Entry.php

Entry.php
<?php
namespace DoYouPhp\PhpDesignPattern\Composite\MyComposite;

/**
 * Componentクラスに相当する
 */
abstract class Entry
{
    private $id;
    private $name;

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

     // 子要素を追加する
     // 抽象メソッドのため、サブクラスで定義する
    abstract public function add(Entry $entry);

    // idとnameを表示する
    public function dump()
    {
        echo 'ID:'.$this->id.',名前:'.$this->name.'<br>'."\n";
    }
}

Leafクラス

Food.php

Food.php
<?php
namespace DoYouPhp\PhpDesignPattern\Composite\MyComposite;

use DoYouPhp\PhpDesignPattern\Composite\MyComposite\Entry;

/**
 * Leafクラスに相当する
 */
class Food extends Entry
{
    public function __construct($id, $name)
    {
        parent::__construct($id, $name);
    }

    // 子要素を追加する
    // 抽象メソッドのため、必ず定義する
    // Leafクラスは子要素を持たないので、例外を発生させる
    public function add(Entry $entry)
    {
        throw new \Exception('このメソッドは使用できません');
    }
}

Compositeクラス

Item.php

Item.php
<?php
namespace DoYouPhp\PhpDesignPattern\Composite\MyComposite;

use DoYouPhp\PhpDesignPattern\Composite\MyComposite\Entry;

/**
 * Compositeクラスに相当する
 */
class Item extends Entry
{
    private $entries;

    public function __construct($id, $name)
    {
        parent::__construct($id, $name);
        $this->entries = array();
    }

    // 子要素を追加する
    public function add(Entry $entry)
    {
        array_push($this->entries, $entry);
    }

    // 組織ツリーを表示する
    // 自分自身と保持している子要素を表示する
    public function dump()
    {
        // ここでidとnameを表示する
        parent::dump();

        foreach ($this->entries as $entry) {
            // 再帰処理を確認する
            // echo '<pre>';
            // var_dump($entry);
            // echo '</pre>';

            // 再帰処理を行い、各要素を表示する
            $entry->dump();
        }
    }
}

Clientクラス

my_client.php

my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Composite\MyComposite;

require dirname(dirname(__DIR__)).'/vendor/autoload.php';

use DoYouPhp\PhpDesignPattern\Composite\MyComposite\Item;
use DoYouPhp\PhpDesignPattern\Composite\MyComposite\Food;

// rootを作成する
$root_entry = new Item(1, 'food');
$root_entry->add(new Food(11, 'orange'));

// root直下に加える
$group1 = new Item(2, 'fruits');
$group1->add(new Food(21, 'banana'));

// fruits直下に加える
$group2 = new Item(3, 'red_fruits');
$group2->add(new Food(31, 'apple'));

// red_fruits直下に加える
$group3 = new Item(4, 'red_small_fruits');
$group3->add(new Food(41, 'strawberry'));
$group2->add($group3);

$group1->add($group2);
$root_entry->add($group1);

// food直下に加える
$group4 = new Item(5, 'vegetables');
$group4->add(new Food(51, 'tomato'));
$root_entry->add($group4);

// 構造を出力する
$root_entry->dump();

// Foodクラスはaddメソッドが使用できないことを確認する
try {
    $group5 = new Food(52, 'onion');
    $group5->add(new Food(53, 'cabbage'));
} catch (\Exception $e) {
    echo $e->getMessage();
}