1. Home
  2. PHP
  3. DesignPatterns
  4. Strategyパターン - PHPデザインパターン

Strategyパターン - PHPデザインパターン

  • 公開日
  • 更新日
  • カテゴリ:DesignPatterns
  • タグ:PHP,Behavior,DesignPatterns,Strategy
Strategyパターン - PHPデザインパターン

Strategy パターン(ストラテジー・パターン)は、振る舞いに関するデザインパターン手法の1つで、戦略部分(アルゴリズム等)をクラス単位で定義(カプセル化)する事で、その切り替えや追加・拡張を容易にする処理モデルです。

Strategy パターンの基本的なクラス図は以下になります。

わりとシンプルですが、敢えて言うなら共通のインタフェースを持つ事を忘れてはいけないのがポイントです。

実装例

今回は、為替レート変換を例に実装します。日本円を外国為替へ変換する PHP アプリケーションがあったとして、それはドル・ユーロ・ポンドへの変換機能を持っています。何も考えずに実装すると、以下のようになります。

まずは変換を行うクラスを定義します。

App/Exchanges/MoneyExchange.php
<?php

namespace App\Exchanges;

class MoneyExchange
{
    private $yen;

    private $doll_rate = 1.1048;
    private $euro_rate = 1.2978;
    private $pond_rate = 1.4681;

    private $doll_symbol = 'USD';
    private $euro_symbol = 'EUR';
    private $pond_symbol = 'GBP';


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

    public function currencyConversion($country)
    {
        $ret = 0;

        if ($country === 'usa') {
            $ret = $this->yen * $this->doll_rate;
        } else if ($country === 'euro') {
            $ret = $this->yen * $this->euro_rate;
        } else if ($country === 'england') {
            $ret = $this->yen * $this->pond_rate;
        } else {
            $ret = false;
        }

        return $ret;
    }

    public function symbol($money, $country)
    {
        $ret = '';

        if ($country === 'usa') {
            $ret = sprintf('%s %.2f', $this->doll_symbol, $money);
        } else if ($country === 'euro') {
            $ret = sprintf('%s %.2f', $this->euro_symbol, $money);
        } else if ($country === 'england') {
            $ret = sprintf('%s %.2f', $this->pond_symbol, $money);
        } else {
            $ret = false;
        }

        return $ret;
    }
}
  • メンバ変数に日本円、そしてそれぞれの通貨のレートと単位を持たせています。
  • コンストラクタで日本円を受け取り格納しています。
  • currencyConversion() メソッドでは、引数で指定された国の通貨へ変換し返却しています。
  • symbol() メソッドでは、引数で指定された国に従って、通貨に単位を付与して返却しています。

ここでの機能としては

  • 日本円を各国の為替レートに従って変換する
  • 数値に通貨単位を付与する

の2つの機能を備えています。

行っている事が単純なので、まあ内容は理解できますが、if の連続を持つメソッドばかりで読みにくいです。(そして開いた瞬間にテンションが下がるやつ)

今回はまだ機能が単純である事と、扱っている通貨が3つだけだったのでよかったですが、為替の変換というのは双方向で行えるべきですし、リアルタイムでレートは変動していくし、と、もっともっと複雑な機能が増えていったらどうでしょうか。そして、扱う通貨ももっと増えたら。このクラスはおそらく破綻します。

一応、このクラスをクライアント側で利用するまでを紹介しておきます。

index.php
<?php
use App\Exchanges\MoneyExchange;

$jp_yen = 1000;

$money = new MoneyExchange($jp_yen);

echo sprintf('JPY %d<br>↓', $jp_yen);
echo '<br>';
echo $money->symbol($money->currencyConversion('usa'), 'usa');
echo '<br>';
echo $money->symbol($money->currencyConversion('euro'), 'euro');
echo '<br>';
echo $money->symbol($money->currencyConversion('england'), 'england');
echo '<br>';

いちいち国を渡しているあたりがいけすきません。国名を変数に格納しても、なんだかスッキリしない感じです。ちなみに結果は以下になります。

さて、そこで満を持して登場するのが、この Strategy パターンです。では、これらの機能を Strategy パターンを用いて実装していきます。

まずはインターフェイスを定義します。

App/Interfaces/ExchangeInterface.php
<?php

namespace App\Interfaces;

interface ExchangeInterface
{
    public function currencyConversion();
    public function symbol($money);
}

為替変換メソッドである currencyConversion() と、為替単位付与メソッドの symbol() を宣言しています。

次に、このインターフェイスを実装したそれぞれの具象クラス群です。ドル・ユーロ・ポンドへの変換クラスを独立して作成します。

App/Exchanges/DollarExchange.php
<?php

namespace App\Exchanges;

use App\Interfaces\ExchangeInterface;

class DollarExchange implements ExchangeInterface
{
    private $yen = 0;

    private $rate = 1.1048;

    private $symbol = 'USD';

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

    public function currencyConversion()
    {
        return $this->yen * $this->rate;
    }

    public function symbol($money)
    {
        return sprintf('%s %.2f', $this->symbol, $money);
    }
}
App/Exchanges/EuroExchange.php
<?php

namespace App\Exchanges;

use App\Interfaces\ExchangeInterface;

class EuroExchange implements ExchangeInterface
{
    private $yen = 0;

    private $rate = 1.2978;

    private $symbol = 'EUR';

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

    public function currencyConversion()
    {
        return $this->yen * $this->rate;
    }

    public function symbol($money)
    {
        return sprintf('%s %.2f', $this->symbol, $money);
    }
}
App/Exchanges/PoundExchange.php
<?php

namespace App\Exchanges;

use App\Interfaces\ExchangeInterface;

class PoundExchange implements ExchangeInterface
{
    private $yen = 0;

    private $rate = 1.4681;

    private $symbol = 'GBP';

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

    public function currencyConversion()
    {
        return $this->yen * $this->rate;
    }

    public function symbol($money)
    {
        return sprintf('%s %.2f', $this->symbol, $money);
    }
}

これだけでもかなりすっきりしたと思います。共通インターフェイスという約束を以てそれぞれが実装されているのでコードの見通しもよく、何よりシンプルです。

そして、これらを利用するクライアント側です。

index.php
<?php

use App\Exchanges\DollarExchange;
use App\Exchanges\EuroExchange;
use App\Exchanges\PoundExchange;

$jp_yen = 1000;

$doller = new DollarExchange($jp_yen);
$euro = new EuroExchange($jp_yen);
$pond = new PoundExchange($jp_yen);

echo sprintf('JPY %d<br>↓', $jp_yen);
echo '<br>';
echo $doller->symbol($doller->currencyConversion());
echo '<br>';
echo $euro->symbol($euro->currencyConversion());
echo '<br>';
echo $pond->symbol($pond->currencyConversion());
echo '<br>';

そして、結果は以下になります。

デザインパターンを使っていない場合と結果は変わらず、Strategy パターンを用いて構造を変更する事ができました。

まとめ

Strategy = 戦略を意味する通り、Strategy パターンは、その処理についてを扱うデザインパターンです。

メソッドの中でアルゴリズムを if や witch などの条件分岐で切り替えると、処理が煩雑になりメンテナンス性も低下してきますが、Strategy パターンを用いて「1つの戦略を1つのクラスで作成し、戦略変更時はクラスを変更する」といった手法をとると見通しが各段に良くなります。機能拡張や切り替えも各段に容易になるのもうれしいポイントです。

戦略戦略言いましたが、デザインパターンとはまさに、PHP や Java などのプログラミング言語版「孫子の兵法」です。是非試してみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級