Menu
Coddy logo textTech

Compositeパターン

CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 80/91。

Compositeパターンは、オブジェクトをツリー構造に組み合わせ、その構造を個々のオブジェクトであるかのように扱える構造設計パターンです。単一のオブジェクトとオブジェクトのグループを統一的に扱いたい、部分と全体の階層構造を表現するのに最適です。

ファイルシステムについて考えてみましょう。folder にはファイルや他の folder を含めることができます。

合計サイズを計算するときは、ファイルとフォルダーの両方で同じメソッドを呼び出し、フォルダーがその内容を再帰的に合計するようにします。Composite パターンは、葉(ファイル)と composites(フォルダー)の両方に同じインターフェースを与えることで、これをシームレスに実現します。

<?php
interface FileSystemItem {
    public function getName(): string;
    public function getSize(): int;
}

class File implements FileSystemItem {
    public function __construct(
        private string $name,
        private int $size
    ) {}
    
    public function getName(): string {
        return $this->name;
    }
    
    public function getSize(): int {
        return $this->size;
    }
}

class Folder implements FileSystemItem {
    private array $items = [];
    
    public function __construct(private string $name) {}
    
    public function getName(): string {
        return $this->name;
    }
    
    public function add(FileSystemItem $item): void {
        $this->items[] = $item;
    }
    
    public function getSize(): int {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item->getSize();
        }
        return $total;
    }
}

これで、ツリー構造を構築し、統一的に扱えるようになりました。

<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));

$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));

$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));

echo $root->getSize();

出力:

1810

クライアントコードは、単一のfileを扱っているのか、深くネストされたfolder構造を扱っているのかを気にせずにgetSize()を呼び出します。このパターンは再帰を内部で処理するため、複雑な階層構造も簡単に扱えます。

challenge icon

チャレンジ

簡単

Composite Pattern を使用して組織図システムを構築しましょう。Companies には、departments が employees や他の sub-departments を contain できる階層構造があります。これは、組織全体で合計を一様に計算したい場合に最適なツリー構造です。

コードを4つのファイルに分けて構成します。

  • OrganizationUnit.php: 個々の employees と departments の両方が実装する OrganizationUnit interface を定義します。2つのメソッド、getName(): stringgetSalary(): int を持たせます。この common interface により、コストを計算するときに、単一の employees と departments 全体を同じ方法で扱えます。
  • Employee.php: OrganizationUnit interface を読み込み、それを実装する Employee class を作成します。employee は組織ツリーの leaf node です。name と salary を持ちますが、他の units を contain することはできません。constructor promotion を使用して name と salary を受け取り、両方の interface メソッドを実装します。
  • Department.php: OrganizationUnit interface を読み込み、それを実装する Department class を作成します。department は employees と他の departments の both を contain できる composite です。department には次の処理を実装します。
    • constructor で name を受け取る
    • getName() を実装して department の name を返す
    • employees または sub-departments を add するための add(OrganizationUnit $unit): void メソッドを持つ
    • getSalary() を実装して、contain するすべての units の total salary を返す(内部にあるすべてを再帰的に合計する)
  • main.php: Employee と Department のファイルを読み込みます。入力として、organization structure を表す JSON string を1つ受け取ります。

    JSON は次の format になります。

    {"Engineering": [{"name": "Alice", "salary": 80000}, {"name": "Bob", "salary": 75000}], "Sales": [{"name": "Carol", "salary": 65000}]}

    各 key は department name で、その value は name と salary を持つ employees の array です。

    "Company" という root Department を Create します。JSON 内の各 department について Department を作成し、その employees を add してから、その department を Company に add します。最後に、会社全体の salary を次の format で出力します。

    Total Salary: [amount]

Composite Pattern の美しい点は、Company department で getSalary() を呼び出すだけで、すべての nested departments と employees の合計が自動的に計算されることです。client code は内部構造を知る必要がありません。

自分で試してみよう

<?php
require_once 'Employee.php';
require_once 'Department.php';

// JSON入力を読み取る
$input = trim(fgets(STDIN));
$data = (array)json_decode($input, true);

// TODO: "Company"という名前のルートDepartmentを作成する

// TODO: JSONデータの各departmentをループする
// 各departmentについて:
//   - 新しいDepartmentオブジェクトを作成する
//   - 各employeeをそのdepartmentに追加する
//   - そのdepartmentをCompanyに追加する

// TODO: 合計給与を次の形式で出力する: Total Salary: [amount]

?>
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: PHPオンラインコンパイラ