RitoLabo

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

  • 公開:
  • カテゴリ: PHP DesignPatterns
  • タグ: PHP,Behavior,DesignPatterns,Strategy

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

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

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

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

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