概要

個々のオブジェクト(葉)とオブジェクトの集合(枝)を同じ様に扱うことができる様にするパターン。

ここで、定義されている同じ様に扱うことができるというのは、アクセス方法が同じメソッドを用意するということです。

利点

  • 木構造を表す時に便利
  • 個々のオブジェクト(葉)と集合オブジェクト(枝)が利用する抽象に依存することで、クライアント側が枝か葉かを意識することなく実装することができる。
  • 枝や葉を増やすのが容易

ショッピングシステムの売り上げ管理するオブジェクトを表現する。
商品を表すProductクラスと、 注文を表すOrderクラスより料金が取得できる。

UML

枝(Order)と葉(Product)が同じ抽象を継承している。

PHPで書いてみる

PriceCalculatable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract class PriceCalculatable {
private $name;

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

abstract public function getPrice() : int;

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

public function add(PriceCalculatable $calculatable)
{
throw new LogicException('集計可能なオブジェクトを追加することはできません。');
}
}

Product

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Product extends PriceCalculatable
{
private $price;

public function __construct(string $name, int $price)
{
parent::__construct($name);
$this->price = $price;
}

public function getPrice() : int
{
return $this->price;
}
}

Order

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
class Order extends PriceCalculatable implements IteratorAggregate
{
private $price;
private $calculatables = [];

public function __construct(string $name)
{
parent::__construct($name);
}

public function add(PriceCalculatable $calculatable) // 抽象に依存させる
{
$this->calculatables[] = $calculatables;
}

public function getIterator() {
foreach ($this->calculatables $calculatable) {
yield $calculatable;
}
}

public function getPrice() : int
{
$total_price = 0;
foreach ($this as $calculatable) {
// OrderでもPriceでも同じ様に扱うことができるので、金額を取得するだけで良い
$total_price += $calculatable->getPrice();
}

return $total_price;
}
}

枝を表すOrderクラスと葉を表すProductクラスで同じAPIを提供する。

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$tamade_order = new Order('5/21 スーパー玉出');
$seven_order = new Order('5/21 セブンイレブン');

$tamade_order->add(new Product('牛肉', 1135));
$tamade_order->add(new Product('豚肉', 350));
$tamade_order->add(new Product('キャベツ', 200));

$seven_order->add(new Product('シーチキンおにぎり', 100));
$seven_order->add(new Product('ペヤング', 275)); // 枝(Order)の中に葉(Product)を入れるパターン

$may_order = new Order('5/21 注文データ');
$may_order->add($tamade_order); // 枝(Order)の中に枝(Order)をインジェクトするパターン
$may_order->add($seven_order);

$may_order->getPrice();

まとめ

  1. 枝と葉が利用する抽象に依存する。
  2. 枝と葉で同じアクセス方法を提供する。
  3. クライアント側では、枝なのか葉なのかは意識する必要があまりない。

参考

Wiki Pedia
参考