Edited at

PHPでStrategyパターン

More than 3 years have passed since last update.

GoFパターンの中でも特に有名なStrategyパターンについておさらいします。


strategyという単語は「戦略」「作戦」「方針」「方策」などの意味があります。

Strategyパターンは、この「作戦」や「戦略」を1つのクラスにまとめ、「作戦」や「戦略」の単位で切り替えられるようにするパターンです。


活用例としてはxmlとcsvにようにファイル形式が違う読み込みを行うとき、同じロジックでA社のAPIとB社のAPIを使う可能性があるとき、などなど、大枠の使用用途は決まっているものの、詳細までは決まっていないときなどに柔軟に実装を変えられるようにしたものです。

形は下記のようなかんじです。

20100822162717.png

戦略A,B,Cのどれを採用するかわからない場合は、Strategyインターフェースを作成し、大枠の仕様を策定し、ContextクラスからはStrategyインターフェースを実装したクラスを呼び出すようにしておきます。こうすることで、


  • 戦略Aを採用したい場合

class Client {

$strategy = new ConcreteStrategyA;
$context = new Context($strategy);
$context->doMethod1();
$context->doMethod2();

}


  • 戦略Bを採用したい場合

class Client {

$strategy = new ConcreteStrategyB;
$context = new Context($strategy);
$context->doMethod1();
$context->doMethod2();

}


  • 戦略Cを採用したい場合

class Client {

$strategy = new ConcreteStrategyC;
$context = new Context($strategy);
$context->doMethod1();
$context->doMethod2();

}

のようにすることができ、クライアント側の変更部分はnew ConcreteStrategyXの部分だけでよくなります。

ここで一旦全体をみてみると、ぱっと見$contextがいらなそうにみえます。


  • 戦略Aを採用したい場合

class Client {

$strategy = new ConcreteStrategyA;
$strategy->doMethod1();
$strategy->doMethod2();

}

これでも良さそうに思えます。しかしこれだとdoMethod1()が戦略B,Cにも実装されている保証がないので、new ConcreteStrategyXの部分を編集するだけでは不十分で、doMethod1()doMethod2()

各戦略クラスに実装されていることを確認しなくてはいけなくなってしまいます。こういった状況をClientConcreteStrategyAに依存している、といいますが、特定の実装に対して依存しないようにするために作られたのがcontext classです。

さて、話を本題に戻して、Strategyパターンの例をみていきます。今回は特定文字列のハッシュ値を求めたいんだけど、いろんな方法があって、まだ実装はきまっていないです、企画次第です、みたいな状況を想定しようと思います。

まずそれぞれの実装を見ていきます。


getHashByMd5.php

class getHashByMd5 implements hashInterface

{
public function getHash($str)
{
return md5($str);
}
}


getHashBySha1.php

class getHashBySha1 implements hashInterface

{
public function getHash($str)
{
return sha1($str);
}
}

これらはhashInterfaceを実装しています。


hashInterface

class hashInterface()

{
public function getHash($str);
}

つぎに依存関係を解消するcontextクラスです。


hashContext.php

class hashContext

{
private $strategy;

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

public function getHash($str)
{
return $this->strategy->getHash($str);
}
}


最後にこれらを利用するクライアントです。


Client.php

// md5でhash値を得たいとき

class Client
{
$strategy = new getHashByMd5;
$hashContext = new hashContext($strategy);
var_dump($hashContext->getHash('sample text'));
}

// sha1でhash値を得たいとき
class Client
{
$strategy = new getHashBySha1;
$hashContext = new hashContext($strategy);
var_dump($hashContext->getHash('sample text'));
}