Decorator パターンとは
このパターンは、既存のオブジェクトに新しい機能や振る舞いを動的に追加することを可能にする。
スポンジケーキが1つあるとする。クリームを塗れば何も載っていないショートケーキができあがる。いちごを並べれば、ストロベリーショートケーキができあがる。いずれにおいてもベースとなるのはスポンジケーキである。様々なデコレーションを施すことで、様々な目的にあったケーキとなる。
オブジェクトもこのような性質がある。まず中心となるベースオブジェクトがある。それに飾り付けとなる機能を一皮一皮かぶせていくことで、より目的にあったオブジェクトに仕上げていく。このようなオブジェクトにデコレーションを施していくようなデザインパターンをDecoratorパターンと呼ぶ。
example
文字列の周りへ飾り枠をつけて表示するサンプルコード。
Display: 文字列表示用抽象クラス
<?php abstract class Display { abstract public function getColumns(): int; abstract public function getRows(): int; abstract public function getRowText(int $row); public function show() { for ($i = 0; $i < $this->getRows(); $i++) { echo $this->getRowText($i) . PHP_EOL; } } }
StringDisplay: 1行だけからなる文字列表示用クラス
<?php class StringDisplay extends Display { private $string; public function __construct(string $string) { $this->string = $string; } public function getColumns(): int { return strlen($this->string); } public function getRows(): int { return 1; } public function getRowText(int $row) { if ($row === 0) { return $this->string; } return null; } } ~
Border: 「飾り枠」を表す抽象クラス
<?php abstract class Border extends Display { protected $display; protected function __construct(Display $display) { $this->display = $display; } }
SideBorder: 左右にのみ飾り枠をつけるクラス
<?php class SideBorder extends Border { private $borderChar; public function __construct(Display $display, string $ch) { parent::__construct($display); $this->borderChar = $ch; } public function getColumns(): int { return 1 + $this->display->getColumns() + 1; } public function getRows(): int { return $this->display->getRows(); } public function getRowText(int $row): string { return $this->borderChar . $this->display->getRowText($row) . $this->borderChar; } }
FulBorder: 上下左右に飾り枠をつけるクラス
<?php class FullBorder extends Border { public function __construct(Display $display) { parent::__construct($display); } public function getColumns(): int { return 1 + $this->display->getColumns() + 1; } public function getRows(): int { return 1 + $this->display->getRows() + 1; } public function getRowText(int $row): string { if ($row === 0) { return "+" . $this->makeLine('-', $this->display->getColumns()) . "+"; } elseif ($row === $this->display->getRows() + 1) { return "+" . $this->makeLine('-', $this->display->getColumns()) . "+"; } return "|" . $this->display->getRowText($row - 1) . "|"; } private function makeLine(string $ch, int $count): string { $buf = ""; for ($i = 0; $i < $count; $i++) { $buf.= $ch; } return $buf; }
Main
<?php $b1 = new StringDisplay("Hello, world."); $b2 = new SideBorder($b1, '#'); $b3 = new FullBorder($b2); $b1->show(); $b2->show(); $b3->show(); $b4 = new SideBorder( new FullBorder( new FullBorder( new SideBorder( new FullBorder( new StringDisplay("こんにちは。") ), '*' ) ) ), '/' ); $b4->show();
実行結果
$ php Main.php Hello, world. #Hello, world.# +---------------+ |#Hello, world.#| +---------------+ /+-------------------+/ /|+-----------------+|/ /||*+-------------+*||/ /||*|Hello, world!|*||/ /||*+-------------+*||/ /|+-----------------+|/ /+-------------------+/
ソースコード
役割
Component
機能を追加するときの核となる役。デコレーションする前のスポンジケーキ(ベース)部分にあたる。インターフェースだけを定める。サンプルコードにおけるDisplayクラス。
ConcreteComponent
具体的な実装をしているスポンジケーキ。サンプルコードにおけるStringDisplayクラス。
Decorator
Component役と同じインターフェースを持つ。さらに、Decorator役が飾る対象となるComponentを持つ。この役は、自分が飾っている対象を知っている。サンプルコードにおけるBorderクラス。
ConcreteDecorator
具体的なDecoratorの役。サンプルコードにおけるSideBorder, FullBorderクラス。
まとめ
透過的インターフェース
- Decoratorパターンでは飾り枠と中身を同一視している。サンプルコードでは、飾り枠を表すBorderクラスが、中身を表すDisplayクラスのサブクラスになっているところで、その同一視が表現されている。
- 飾り枠を使って中身を包んでも、インターフェースは「透過的」(隠れていない状態)であるため、飾り枠をたくさん使って包んでも、インターフェースは全く変更されない。
包まれるもの(中身)を変えずに機能追加ができる
Decoratorパターンでは、委譲が使われている。飾り枠に対してやってきた要求はその中身にたらい回し(委譲)される。
動的な機能追加ができる
委譲でクラス間をゆるやかに結合しているため。
関連パターン
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (399件) を見る