PHP7でデザインパターン入門11/23 Compositeパターン

Composite パターンとは

「構造に関するパターン」に属する。Composite パターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。

Composite パターン - Wikipedia

コンピュータにおけるディレクトリのように、ファイルとディレクトリをまとめて扱うような場合、容器(ディレクトリ)と中身(ファイル)を同じ種類として扱うと便利な場合がある。容器の中には、中身を入れてもさらに新たな容器を入れても良く、このように入れ子になった構造、再帰的な構造を作る事ができる。

Comosite パターンとは上記のような容器と中身を同一視し、再帰的な構造を作るデザインパターンである。

example

サンプルプログラムはファイルとディレクトリを模式的に表現したもの。

  • File: ファイルを表す
  • MyDirectory: ディレクトリを表す(PHPではDirectoryクラスは標準で定義済みのためMyDirectoryとした)
  • Entry: FileとMyDirectoryの両者をとりまとめるスーパークラス

Entry 抽象クラス

<?php
abstract class Entry
{
    abstract public function getName(): string;
    abstract public function getSize(): int;

    public function add(Entry $entry)
    {
        throw new RuntimeException();
    }

    public function printList()
    {
        $this->printListWithPrefix("");
    }

    abstract protected function printListWithPrefix(string $prefix);

    public function __toString(): string
    {
        return $this->getName() . " (" . $this->getSize() . ")";
    }
}

MyDirectory クラス

<?php
class MyDirectory extends Entry
{
    private $name;
    private $directory = [];

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

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

    public function getSize(): int
    {
        $size = 0;
        foreach ($this->directory as $v) {
            $size += $v->getSize();
        }
        return $size;
    }

    public function add(Entry $entry): Entry
    {
        $this->directory[] = $entry;
        return $this;
    }

    protected function printListWithPrefix(string $prefix)
    {
        echo $prefix . "/" . $this . PHP_EOL;
        foreach ($this->directory as $v) {
            $v->printListWithPrefix($prefix . "/" . $this->name);
        }
    }
}

File クラス

<?php

class File extends Entry
{
    private $name;
    private $size;

    public function __construct(string $name, int $size)
    {
        $this->name = $name;
        $this->size = $size;
    }
    public function getName(): string
    {
        return $this->name;
    }
    public function getSize(): int
    {
        return $this->size;
    }
    protected function printListWithPrefix(string $prefix)
    {
        echo $prefix . "/" . $this . PHP_EOL;
    }
}

Main

<?php
try {
    echo "Making root entries..." . PHP_EOL;
    $rootdir = new MyDirectory("root");
    $bindir = new MyDirectory("bin");
    $tmpdir = new MyDirectory("tmp");
    $usrdir = new MyDirectory("usr");
    $rootdir->add($bindir);
    $rootdir->add($tmpdir);
    $rootdir->add($usrdir);
    $bindir->add(new File("vi", 10000));
    $bindir->add(new File("latex", 20000));
    $rootdir->printList();

    echo "" . PHP_EOL;
    echo "Making user entries..." . PHP_EOL;
    $yuki = new MyDirectory("yuki");
    $hanako = new MyDirectory("hanako");
    $tomura = new MyDirectory("tomura");
    $usrdir->add($yuki);
    $usrdir->add($hanako);
    $usrdir->add($tomura);
    $yuki->add(new File("diary.html", 100));
    $yuki->add(new File("Composite.java", 200));
    $hanako->add(new File("memo.tex", 300));
    $tomura->add(new File("game.doc", 400));
    $tomura->add(new File("junk.mail", 500));
    $rootdir->printList();
} catch (RuntimeException $e) {
    var_dump($e->getTrace());
}

実行結果

$ php Main.php
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

ソースコード

github.com

役割

  • Leaf役: 「中身」を表すクラス。この役の中には他のものを入れることはできない。サンプルコードにおけるFileクラス
  • Composite役: 「容器」を表すクラス。Leaf役やComposite役(自身)を入れることができる。サンプルコードにおけるMyDirectoryクラス
  • Component役: Leaf役とComposite役を同一視するための役。Component役は、Leaf役とComposite役に共通のスーパークラスとして実現する。サンプルコードにおけるEntryクラス
  • Client役: Compositeパターンの利用者。サンプルコードにおけるMain.php

まとめ

  • 再帰的構造
  • 複数と単数の同一視

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門