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


引用記事

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

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

Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Decorator~かぶせて機能UP

概要

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

構成要素

Componentクラス

  • 拡張される機能を定義した抽象クラス。

ConcreteComponentクラス

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

Decoratorクラス

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

ConcreteDecoratorクラス

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

実演

処理の流れ

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

ファイル構造

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']);