※学習用メモ デザインパターン:Decorator編[PHP]

# Decoratorパターンとはどういうものか

Decoratorとは**装飾者**という意味です。
このパターンは既存のオブジェクトを新しいDecoratorオブジェクトでラップすることで機能やその状態の拡張を実現します。
また、ラップされていることで、基の状態を維持する事につながり、１つのベースに対して状態のバリエーションを作り出す事も出来ます。

>Decoratorパターンの方針は、既存のオブジェクトを新しいDecoratorオブジェクトでラップすることである。その方法として、Decoratorのコンストラクタの引数でラップ対象のComponentオブジェクトを読み込み、コンストラクタの内部でそのオブジェクトをメンバに設定することが一般的である。(wikipediaより)

[参考サイト]
https://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
https://www.ritolab.com/entry/132
https://qiita.com/i-tanaka730/items/a0f53d70b0830cfd150b

# Decoratorパターンを用いるメリット

・このパターンはComponent、Decorator双方が同様のインターフェースを有しており、あらゆるDecoratorで包めば包むほど機能が追加されていきます。その際に包まれる側のオブジェクトを変更する必要がないため、変更を行うことなく機能の追加を用意に行うことができます。

# サンプルコード

## 仕様

・ガチャを引く
・基本として、与えられた配列をset,get,その中からランダムで一つ抽出する機能をもつ。
・追加機能として、「特定のアイテムを強化版に」、「特定のアイテムの獲得率を〇倍」という機能を持つことが可能。

ガチャのインターフェース

```GachaInterface.php
interface GachaInterface
{
    public function getGachaContents();
    public function setGachaContents($gachaContentArray);
    public function getGachaName();
    public function setGachaName($gachaName);
}
```

ガチャ情報のベース(Component)

```BaseGacha.class.php
class BaseGacha implements GachaInterface
{
    private $gachaContentArray = array();
    private $gachaName = '';

    public function getGachaContents()
    {
        return $this->gachaContentArray;
    }

    public function setGachaContents($gachaContentArray)
    {
        $this->gachaContentArray = $gachaContentArray;
    }

    public function getGachaName()
    {
        return $this->gachaName;
    }

    public function setGachaName($gachaName)
    {
        $this->gachaName = $gachaName;
    }
}
```

ガチャのDecoratorクラス

```GachaDecorator.class.php
abstract class GachaDecorator implements GachaInterface
{
    private $obj;

    public function __construct(GachaInterface $obj)
    {
        $this->obj = $obj;
    }

    public function getGachaContents()
    {
        return $this->obj->getGachaContents();
    }

    public function setGachaContents($gachaContentArray)
    {
        $this->obj->setGachaContents($gachaContentArray);
    }

    public function getGachaName()
    {
        return $this->obj->getGachaName();
    }

    public function setGachaName($gachaName)
    {
        $this->obj->setGachaName($gachaName);
    }
}
```

特定アイテムのみ強化する

```EvolutionGacha.class.php
class EvolutionGacha extends GachaDecorator
{
    private $itemName; // アイテム名

    public function __construct(GachaInterface $obj, $itemName)
    {
        parent::__construct($obj);
        $this->itemName = $itemName;
    }

    public function getGachaContents()
    {
        $resultArray = array();

        foreach (parent::getGachaContents() as $itemData) {
            $itemDataArray = array();
            if ($itemData['name'] == $this->itemName) {
                $itemDataArray['name'] = $itemData['name'] . '+';
            } else {
                $itemDataArray['name'] = $itemData['name'];
            }
            $itemDataArray['weight'] = $itemData['weight'];

            $resultArray[] = $itemDataArray;
        }

        return $resultArray;
    }

    public function getGachaName()
    {
        return sprintf('【%s強化中！】' . parent::getGachaName(), $this->itemName);
    }
}
```

特定アイテムの出現率を〇倍に

```MagnificationGacha.class.php
class MagnificationGacha extends GachaDecorator
{
    private $itemName; // アイテム名
    private $magnification = 0; // 倍率

    public function __construct(GachaInterface $obj, $itemName, $magnification)
    {
        parent::__construct($obj);
        $this->itemName = $itemName;
        $this->magnification = $magnification;
    }

    public function getGachaContents()
    {
        $resultArray = array();

        foreach (parent::getGachaContents() as $itemData) {
            $itemDataArray = array();
            if ($itemData['name'] == $this->itemName) {
                $itemDataArray['weight'] = $itemData['weight'] * $this->magnification;
            } else {
                $itemDataArray['weight'] = $itemData['weight'];
            }
            $itemDataArray['name'] = $itemData['name'];

            $resultArray[] = $itemDataArray;
        }
        return $resultArray;
    }

    public function getGachaName()
    {
        return sprintf('【%s出現率%s倍中！】' . parent::getGachaName(), $this->itemName, $this->magnification);
    }
}
```

## 使ってみる

あらかじめデータを用意し、ガチャ結果を取得

```php
$gachaContentArray = array(
    array(
        'weight' => 10,
        'name' => '剣',
    ),
    array(
        'weight' => 10,
        'name' => '盾',
    ),
    array(
        'weight' => 10,
        'name' => '鎧',
    ),
    array(
        'weight' => 10,
        'name' => '靴',
    ),
    array(
        'weight' => 10,
        'name' => '腕輪',
    ),
);

class DrawGacha
{
    public static function getGachaResult($gachaArray)
    {
        if (! $gachaArray) {
            return array();
        }

        $totalWeight = 0;

        foreach ($gachaArray as $itemData) {
            $totalWeight += $itemData['weight'];
        }

        $num = mt_rand(1, $totalWeight);

        $judgeTotalWeight = 0;
        foreach ($gachaArray as $itemData) {
            $judgeTotalWeight += $itemData['weight'];

            if ($num < $judgeTotalWeight) {
                return $itemData['name'];
            }
        }
    }
}

$baseGacha = new BaseGacha();
$baseGacha->setGachaContents($gachaContentArray);
$baseGacha->setGachaName('アイテムガチャ');

$evolutionGacha = new EvolutionGacha($baseGacha, '剣');
echo $evolutionGacha->getGachaName() , 'の結果:' . DrawGacha::getGachaResult($evolutionGacha->getGachaContents()) . PHP_EOL;

$magnificationGacha = new MagnificationGacha($baseGacha, '盾', 20);
echo $magnificationGacha->getGachaName() , 'の結果:' . DrawGacha::getGachaResult($magnificationGacha->getGachaContents()) . PHP_EOL;

$extremeGacha = new EvolutionGacha(new MagnificationGacha($baseGacha, '靴', 2), '鎧');
echo $extremeGacha->getGachaName() , 'の結果:' . DrawGacha::getGachaResult($extremeGacha->getGachaContents()) . PHP_EOL;

/*結果
【剣強化中！】アイテムガチャの結果:剣+
【盾出現率20倍中！】アイテムガチャの結果:盾
【鎧強化中！】【靴出現率2倍中！】アイテムガチャの結果:鎧+
*/
```

# まとめ

・Decorator パターンは様々な機能追加を柔軟に行いたい場合に利用すると効果を発揮します。