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

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

Strategyとは**戦略**という意味です。
このパターンはアルゴリズム(戦略)を実行する際に、どのアルゴリズムを使用するか選択できるというものとなります。

>Strategy パターンは、アプリケーションで使用されるアルゴリズムを動的に切り替える必要がある際に有用である。Strategy パターンはアルゴリズムのセットを定義する方法を提供し、これらを交換可能にすることを目的としている。Strategy パターンにより、アルゴリズムを使用者から独立したまま様々に変化させることができるようになる。(wikipediaより)

共通のインターフェースを定義することで、使用側(クライアント)はそのインターフェースに伴った処理を実行すればよいので、具体的な実装に依存することがなくなります。


## どういう時に使用可能なのか
・アプリケーションで使用されるアルゴリズムを動的に切り替える必要がある場合

[参考サイト]
https://ja.wikipedia.org/wiki/Strategy_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
https://qiita.com/nooboolean/items/5c0d0f651474ff34130a
https://qiita.com/i-tanaka730/items/4d00c884b7ce1594f42a

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

・アルゴリズムを切り替えるというパターンの性質上、if文やswitch文などの条件分岐をスマートにすることができる。普段であれば場合分けする部分を、具体的なアルゴリズムを記述するクラスに格納することでコードが非常にシンプルになり可読性、拡張性が上がる。

# サンプルコード

## 仕様

競馬の馬券内3頭を選ぶ
・馬情報から各々のアルゴリズムを用いて結果を取得
・今回はランダムと枠順内側から順番

馬情報クラス

```HorseData.class.php
class HorseData
{
    private $age = 0;
    private $name = '';
    private $flameNum = 0;
    private $popularNum = 0;

    public function __construct($age, $name, $flameNum, $popularNum)
    {
        $this->age = $age;
        $this->name = $name;
        $this->flameNum = $flameNum;
        $this->popularNum = $popularNum;
    }

    public function getAge()
    {
        return $this->age;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getFlameNum()
    {
        return $this->flameNum;
    }

    public function getPopularNum()
    {
        return $this->popularNum;
    }
}
```

アルゴリズムの処理の大枠を示すインターフェース

```TripleSingleInterface
interface TripleSingleInterface
{
    public function getResult($horseDataList);
}
```

ランダムで選択

```Random.class.php
class Random implements TripleSingleInterface
{
    public function getResult($horseDataList)
    {
        $resultData = array();
        
        $randomArray = range(1, count($horseDataList));
        shuffle($randomArray);
        
        foreach ($horseDataList as $horseData)
        {
            for ($i = 0; $i < 3; $i++) {
                if ($horseData->getFlameNum() == $randomArray[$i]) {
                    $resultData[$i] = $horseData;
                }
            }
        }
        
        ksort($resultData);
        
        return $resultData;
    }
}
```

枠順昇順に選択

```FrameAscendingOrder.class.php
class FrameAscendingOrder implements TripleSingleInterface
{
    public function getResult($horseDataList)
    {
        $resultData = array();
        
        uasort($horseDataList, function($a, $b){
            return $a->getFlameNum() > $b->getFlameNum();
        });
        
        $i = 0;
        foreach ($horseDataList as $horseData){
            if ($i > 2) {
                break;
            }
            $resultData[$i] = $horseData;
            $i++;
        }
        
        return $resultData;
    }
}
```

三頭選ぶ大枠の処理クラス

```TripleSingle.class.php
class TripleSingle
{
    private $TripleSingleAlgorithm;
    
    public function __construct($TripleSingleAlgorithm)
    {
        $this->TripleSingleAlgorithm = $TripleSingleAlgorithm;
    }
    
    public function getParam($horseDataList)
    {
        return $this->TripleSingleAlgorithm->getResult($horseDataList);;
    }
}
```

## 使ってみる

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

```php
// 本当はDBとかから取得すると良い
$testHorseDataArray = array(
    array(
        'age' => 3,
        'name' => 'コントレイル',
        'flame_num' => 1,
        'popular_num' => 1
    ),
    array(
        'age' => 5,
        'name' => 'アーモンドアイ',
        'flame_num' => 3,
        'popular_num' => 2
    ),
    array(
        'age' => 3,
        'name' => 'デアリングタクト',
        'flame_num' => 4,
        'popular_num' => 3
    ),
    array(
        'age' => 4,
        'name' => 'サートゥルナーリア',
        'flame_num' => 5,
        'popular_num' => 4
    ),
    array(
        'age' => 6,
        'name' => 'キセキ',
        'flame_num' => 2,
        'popular_num' => 5
    ),
);

$testHorseDataList = array();

foreach ($testHorseDataArray as $testHorseData) {
    $testHorseDataList[] = new HorseData($testHorseData['age'], $testHorseData['name'], $testHorseData['flame_num'], $testHorseData['popular_num']);
}

$randomChoice = new Random();
$flameAscendingOrderChoice = new FrameAscendingOrder();

$choice1 = new TripleSingle($randomChoice);
$choice2 = new TripleSingle($flameAscendingOrderChoice);

var_dump($choice1->getParam($testHorseDataList));
var_dump($choice2->getParam($testHorseDataList));

/*結果
array(3) {
  [0]=>
  object(HorseData)#1 (4) {
    ["age":"HorseData":private]=>
    int(3)
    ["name":"HorseData":private]=>
    string(18) "コントレイル"
    ["flameNum":"HorseData":private]=>
    int(1)
    ["popularNum":"HorseData":private]=>
    int(1)
  }
  [1]=>
  object(HorseData)#3 (4) {
    ["age":"HorseData":private]=>
    int(3)
    ["name":"HorseData":private]=>
    string(24) "デアリングタクト"
    ["flameNum":"HorseData":private]=>
    int(4)
    ["popularNum":"HorseData":private]=>
    int(3)
  }
  [2]=>
  object(HorseData)#5 (4) {
    ["age":"HorseData":private]=>
    int(6)
    ["name":"HorseData":private]=>
    string(9) "キセキ"
    ["flameNum":"HorseData":private]=>
    int(2)
    ["popularNum":"HorseData":private]=>
    int(5)
  }
}
array(3) {
  [0]=>
  object(HorseData)#1 (4) {
    ["age":"HorseData":private]=>
    int(3)
    ["name":"HorseData":private]=>
    string(18) "コントレイル"
    ["flameNum":"HorseData":private]=>
    int(1)
    ["popularNum":"HorseData":private]=>
    int(1)
  }
  [1]=>
  object(HorseData)#5 (4) {
    ["age":"HorseData":private]=>
    int(6)
    ["name":"HorseData":private]=>
    string(9) "キセキ"
    ["flameNum":"HorseData":private]=>
    int(2)
    ["popularNum":"HorseData":private]=>
    int(5)
  }
  [2]=>
  object(HorseData)#2 (4) {
    ["age":"HorseData":private]=>
    int(5)
    ["name":"HorseData":private]=>
    string(21) "アーモンドアイ"
    ["flameNum":"HorseData":private]=>
    int(3)
    ["popularNum":"HorseData":private]=>
    int(2)
  }
}
 */
```

# まとめ

・同様なデータから多数の方法を用いて結果を出したい場合は多くあるため、この考え方自体は多様な部分で使用できると感じた。
・余談だが、この競馬のものを機械学習と結び付けて多様なアルゴリズムで切り替えて分析してみるのもおもしろそうだなぁ。。と感じました。（笑）