【Unity】プログラム設計 decoratorパターン!【getComponent】【PHP】

こんばんわ!!(^o^)

今日はプログラム設計の1つ decoratorパターンについてだよ!

Unity触ってる人なら絶対触ったことあるはずのGetComponentはこのdecoratorパターンの概念が使われているのだ!!!(^o^)

と言うわけでGetComponent知ってる人はなんとなくわかるだろうけど、decoratorパターンの概要から実際に簡単な実装までを紹介するよ!

1、decoratorパターンとは
あるオブジェクトに機能を追加したいときに動的に追加できる設計!
今回の実装ではやってないけど動的に機能を削除することもできます!!(^o^)

唐突な仕様変更の対応にも心強い。。。けど唐突な仕様変更はなるべくやめて😭😭

2、実際にどんな場面で使うの🤔
個人的にはこの設計見たときにゲームでいうならキャラクターの装備機能とかにいいのではって思ってます(^o^)
オブジェクトの機能、状態がよく変わるような仕様の場合はこのdecoratorパターンがいいんじゃないかなあ🤔

3、実際に実装してみよう🤔
実際に実装してみよう!ってことで、簡単に仕様的なの
1、好きな顔文字をベースに手をつけたい 
(^u^)/ ← こんな感じに手付ける
2、色がついてるもの、ついてないものと表示させたい

上記の感じで顔文字オブジェクトをベースに記号を付与したり、色をつけたりという機能をつけていきます!

UnityのGetComponentのイメージだとすぐやり方思い浮かびますね!GameObjectを用意してColorコンポーネントつけてTextコンポーネントつけて〜って感じになると思います!

でもGetComponentの機能がないとどう実装すればいいのでしょう??🤔🤔

このパターンを知らないと、おそらくフラグ変数を何個か使って、if文またはcase文で顔文字の状態を確認しながら機能を追加〜〜ってなっていくと思います。他にも機能追加の依頼が来たらフラグ変数も増やして、条件分岐増やして中に処理を書いて〜〜と。。

よく言われる拡張、保守の難しいゴリ押しプログラムになりますね🤔🤔🤔
UnityでもGetComponentで対応できる範囲を越えると、余裕もなくてこういうプログラムになっちゃう人もいると思います!(自分のことです!)

それを回避するためにもdecoratorパターンです!

では実際のコード!PHPです..

 

 

まずはオブジェクト指向の真髄!抽象メソッドの定義!インターフェースと抽象クラスの説明いつかしたほうがいいかな。。。
今回は顔文字の取得とセットだけです!

Component.php

interface Component { public function getEmotion(); public function setEmotion($emotion); } 

次はComponentインターフェースを使って、emotion(顔文字)の取得とセットできるSmileyクラスです!
Smiley.php

require_once('Component.php');

class Smiley implements Component {
    private $emotion = null;

    public function getEmotion() {
        return $this->emotion;
    }

    public function setEmotion($emotion) {
        $this->emotion = $emotion;
    }
}

次にComponentインターフェースを継承してDecoratorクラスを作成です。
このDecoratorを継承して機能(今回でいう色を変えたり、手をつけたりする)クラスを書いていきます!
Decorator.php

require_once('Component.php');
abstract class Decorator implements Component {
    private $compo;

    public function __construct(Component $compo) {
        $this->compo = $compo;
    }

    public function getEmotion() {
        return $this->compo->getEmotion();
    }

    public function getComponent() {
      return get_class($this->compo);
    }

    public function setEmotion($emotion) {
        $this->compo->setEmotion($emotion);
    }

    // コンポーネント名を返す
    public function getClassName() {
        echo get_class($this) , "\n";
    }
}

Decoratorという抽象クラスを継承して手をつけるコンポーネント、AddHandsComponentを作ります
AddHandsComponent.php

require_once('Decorator.php');
class AddHandsComponent extends Decorator {

    // 手をつける
    public function getEmotion() {
        $emotion = parent::getEmotion();
        return "٩". ($emotion) ."ノ";
    }

}

次はAddHandsComponentと同じようにカラーを変える(今回めんどくなって青色固定😠)ChangeColorComponent作成です
ChangeColorComponent.php

require_once('Decorator.php');
class ChangeColorComponent extends Decorator {
    public function getEmotion() {
        $emotion = parent::getEmotion();
        $emotion="<FONT COLOR=\"BLUE\">".$emotion."</FONT>";
        return $emotion;
    }

}

と!ここで機能追加依頼が!!!!!!

顔文字によくある(^o^)の中の英文字を大文字にして口が開くようにしよう!!

察しのいい人はお気づきでしょうが、小文字を大文字にするクラスを作るだけでいいのです!この結合性の低さがミソですね!

というわけで口を開けるクラス
OpenMouthComponent.php

require_once('Decorator.php');
class OpenMouthComponent extends Decorator {

    // 小文字を大文字に変換して返す
    public function getEmotion() {
        $emotion = parent::getEmotion();
        return strtoupper($emotion);
    }

}

はい!できました〜!(^o^)

あとはメイン処理でこれらを動作させましょう!
index.php

require_once('Smiley.php');
require_once('OpenMouthComponent.php');
require_once('AddHandsComponent.php');
require_once('ChangeColorComponent.php');

//顔文字を生成 & セット
$compo_obj = new Smiley();
$compo_obj->setEmotion("(^o^)");

// 口を開かせる(oを大文字にしてるだけ)
$OpenMouth_obj = new OpenMouthComponent($compo_obj);
echo $OpenMouth_obj->getEmotion() . " < ";
// 使っているコンポーネント取得
echo $OpenMouth_obj->getClassName() . "
";
echo "
";

// 顔文字に手を付ける
$AddHands_obj = new AddHandsComponent($compo_obj);
echo $AddHands_obj->getEmotion() . " < ";
// 使っているコンポーネント取得
echo $AddHands_obj->getClassName() . "
";
echo "
";

// 色を変える
$ChangeColor_obj = new ChangeColorComponent($compo_obj);
echo $ChangeColor_obj->getEmotion() . " < ";
// 使っているコンポーネント取得
echo $ChangeColor_obj->getClassName() . "
";
echo "
";

// 口を開かせ、かつ手を付ける
$deco_obj1 = new AddHandsComponent(new OpenMouthComponent($compo_obj));
echo $deco_obj1->getEmotion() . " < ";
echo "口を開かせ、かつ手を付けたよ". "
";
echo "
";

// 全部のコンポーネントをつける
$deco_obj2 = new ChangeColorComponent(new AddHandsComponent(new OpenMouthComponent($compo_obj)));
echo $deco_obj2->getEmotion() . " < ";
echo "口を開かせ、手を付けて色も変えたよ!". "
";
echo "
";

実行結果!!

はい!めっちゃ簡素ですができてますね!
index.phpみてわかると思うのですが、重ねてインスタンス化することで機能を簡単に混ぜることもできます!UnityのComponentも裏の根幹部分は似たような概念が使われているはずです!!

ちなみに条件分岐もしないで同じメソッド名なのに、なんで違う挙動起こるんだってのはインターフェースや抽象クラスを調べて見てね!(今度この辺書くかもだけど)

この設計デザインパターンは汎用性高いと思うのでぜひ使って見てね〜(^o^)ノ