【PHPデザインパターン】13_Decorator～かぶせて機能UP

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

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

> [Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Decorator～かぶせて機能UP](http://d.hatena.ne.jp/shimooka/20141217/1418788239)

# 概要
- Decoratorとは直訳すると「装飾者」。
- オブジェクトに対して機能を柔軟に（動的に）追加したり取り外したりする。
- サブクラス化よりも柔軟な拡張方法を提供する。
- 継承による拡張は責任（機能）を「静的」に追加する。「動的」な追加は、実行中に責任を追加したり外したりできる。

## 構成要素
### Componentクラス
- 拡張される機能を定義した抽象クラス。

### ConcreteComponentクラス
- Componentクラスで定義した機能を基本実装する、飾り付けされる具象クラス。

### Decoratorクラス
- Componentクラスを実装し、さらにプロパティ（メンバ変数）としてComponentクラスのオブジェクトを保持する抽象クラス。
- メソッドの具体的な実装はComponentクラス（ConcreteComponentクラス）へ委譲する。

### ConcreteDecoratorクラス
- Componentクラスに機能を追加するために、Decoratorクラスを継承するクラス。
- 自身のメソッドで親クラスのメソッドを利用しながら機能の拡張（飾り付け）を行う。

# 実演
## 処理の流れ
- ``PlainTextクラス``で文字列をプロパティとして保有する。
- ``TextDecoratorクラス``で``TextDataクラス``のオブジェクトを保有する。
- ``DoubleTextクラス``で文字列を全角に変換する。
- ``UpperTextクラス``で文字列を大文字に変換する。
- TextDataクラスのオブジェクトに対し、再帰処理で各ConcreteDecoratorクラスのメソッドを実行する。

## ファイル構造
```sh
MyDecorator
  ├── DoubleText.php
  ├── PlainText.php
  ├── TextData.php
  ├── TextDecorator.php
  ├── UpperText.php
  └── my_client.php
```

## ソースコード
### Componentクラス
#### TextData.php
```TextData.php
<?php
namespace DoYouPhp\PhpDesignPattern\Decorator\MyDecorator;

/**
 * Componentクラスに相当する
 * インターフェイスのため、サブクラスで実装する
 */
interface TextData
{
    public function getText();
    public function setText($str);
}
```

### ConcreteComponentクラス
#### PlainText.php
```PlainText.php
<?php
namespace DoYouPhp\PhpDesignPattern\Decorator\MyDecorator;

use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\TextData;

/**
 * ConcreteComponentクラスに相当する
 * 編集前のテキストを表すクラス
 */
class PlainText implements TextData
{
    private $textString = null;

    // 文字列を取得する
    // 常にplainの状態で返す
    public function getText()
    {
        return $this->textString;
    }

    // 文字列をセットする
    public function setText($str)
    {
        $this->textString = $str;
    }
}
```

### Decoratorクラス
#### TextDecorator.php
```TextDecorator.php
<?php
namespace DoYouPhp\PhpDesignPattern\Decorator\MyDecorator;

use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\TextData;

/**
 * Decoratorクラス
 * TextDataクラスのオブジェクトをプロパティ(メンバ変数)として保有する
 */
abstract class TextDecorator implements TextData
{
    // TextDataインスタンスを保有する
    private $text;

    public function __construct(TextData $target)
    {
        $this->text = $target;
    }

    // 一番深い階層に存在するPlainTextクラスで保有する文字列が返される
    // 再帰処理を行っている
    // getTextメソッドの戻り値に対して、さらにgetTextメソッドを実行している
    public function getText()
    {
        return $this->text->getText();
    }

     // TextDataインスタンスに文字列をセットする
    public function setText($str)
    {
        $this->text->setText($str);
    }
}
```

### ConcreteDecoratorクラス
#### DoubleText.php
```DoubleText.php
<?php
namespace DoYouPhp\PhpDesignPattern\Decorator\MyDecorator;

use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\TextDecorator;
use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\TextData;

/**
 * ConcreteDecoratorクラスに相当する
 * TextDecoratorクラスを継承する
 */
class DoubleText extends TextDecorator
{
    // インスタンスをセットする
    public function __construct(TextData $target)
    {
        // 親クラスのメソッド
        parent::__construct($target);
    }

    // 文字列を全角文字に変換して返す
    public function getText()
    {
        // 親クラスのメソッド
        $str = parent::getText();
        // 文字列を変換する
        $str = mb_convert_kana($str, "R");

        return $str;
    }
}
```

#### UpperText.php
```UpperText.php
<?php
namespace DoYouPhp\PhpDesignPattern\Decorator\MyDecorator;

use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\TextDecorator;
use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\TextData;

/**
 * ConcreteDecoratorクラスに相当する
 * TextDecoratorクラスを継承する
 */
class UpperText extends TextDecorator
{
    // インスタンスをセットする
    public function __construct(TextData $target)
    {
        // 親クラスのメソッド
        parent::__construct($target);
    }

    // 文字列を大文字に変換して返す
    public function getText()
    {
        // 親クラスのメソッド
        $str = parent::getText();
        // 文字列を変換する
        $str = mb_strtoupper($str);

        return $str;
    }
}
```

### Clientクラス
#### my_client.php
```my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Decorator\MyDecorator;

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

use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\DoubleText;
use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\UpperText;
use DoYouPhp\PhpDesignPattern\Decorator\MyDecorator\PlainText;

function decorate($text, array $decorate = [])
{
    $text_object = new PlainText();
    // plainの文字列がセットされる
    $text_object->setText($text);

    // $text_objectは階層のようになる
    foreach ($decorate as $val) {
        switch ($val) {
        case 'double' :
            $text_object = new DoubleText($text_object);
            break;
        case 'upper':
            $text_object = new UpperText($text_object);
            break;
        }
    }

    // PlainTextクラスが装飾されている状態
    // echo '<pre>';
    // var_dump($text_object);
    // echo '</pre>';

    // 最後に追加したクラスが表示される
    // var_dump(get_class($text_object));

    // 各クラスのオブジェクトのメソッドを実行できる
    echo $text_object->getText().'<br>'."\n";
}

$text = 'Hello, World!';

echo '---plain---'.'<br>'."\n";
decorate($text);

echo '---double---'.'<br>'."\n";
decorate($text, ['double']);

echo '---upper---'.'<br>'."\n";
decorate($text, ['upper']);

// upper,double,plainの順で呼び出される
// 再帰処理が実行されているため、実質はplain, double, upperの順番になる
echo '---double + upper---'.'<br>'."\n";
decorate($text, ['double', 'upper']);
```