環境
- PHP 7.2.18
はじめに
今更ながら、デザインパターンを真面目に理解し始めようとしている2年目エンジニアであります。
まずは事始めに「Strategyパターン」から勉強していこうと思い、この記事を書きました。
普通はJavaで勉強すべきだと思うのですが、Java初心者すぎるので、一番触っているPHPで勉強しています。
ちなみに、会社のブログでも同じような記事を書いたので、ここに貼っておきます。
レガシーコードをぶっ壊す!〜基本のキ編〜
Strategyパターンとは
Strategyとは、「戦略」や「作戦」という意味を持ちます。
この「戦略」や「作戦」毎にクラスを分割して、簡単にそれらを切り替えることができるのが特徴です。
共通のインターフェースを定義し、クラスを使う側(以下クライアント)はそれに伴った処理を実行するので、クライアントは具体的な実装に依存しなくなります。
strategyパターンのクラス図は以下のようなものです。
Strategyパターンは何がいいか
自分が一番Strategyパターンを使うことで受けたメリットは、if文やswitch文などの複雑な条件分岐をスマートにすることです。
普段であれば場合分けしなければならないところを、「戦略」をクラスに格納することによりコードが非常にシンプルになり可読性や拡張性が上がります。
例題の概要
今回は、例として簡単なプログラムを作りました。
URLのクエリパラメーターから、果物の名前を取得して、その果物の情報を表示しています。
イメージ図とクラス図です。
イメージ図
クラス図
ファイル構成
ソースコード
実装
まずはインターフェースの定義
まずはじめに、果物のクラスがどんな実装をしているべきなのかを定義します。
今回は、「名前」「色」「好きか嫌いか」「人気順位」をブラウザに表示するので、それぞれの情報を取得するメソッドを定義します。こんな感じです。
<?php
namespace Interfaces;
interface FruitInterface{
public function getName();
public function getColor();
public function getHasLike();
public function getOrderOfPopularity();
}
この後は、このインターフェースに従って具体的な実装を持つクラス(果物クラス)を実装していきます。
次は具体的な実装を持つクラス(果物クラス)の用意
先ほど定義したインターフェースに従って、それぞれの果物クラスを実装していきます。
例として、OrangeクラスとAppleクラスを記載しておきます。
<?php
namespace Fruits;
require_once('./Interfaces/FruitInterface.php');
use Interfaces\FruitInterface;
class Orange implements FruitInterface {
public function getName(){
return 'みかん';
}
public function getColor(){
return 'orange';
}
public function getHasLike(){
return '好き';
}
public function getOrderOfPopularity(){
return 1;
}
}
<?php
namespace Fruits;
require_once('./Interfaces/FruitInterface.php');
use Interfaces\FruitInterface;
class Apple implements FruitInterface {
public function getName(){
return 'リンゴ';
}
public function getColor(){
return 'red';
}
public function getHasLike(){
return '好き';
}
public function getOrderOfPopularity(){
return 3;
}
}
どちらも定義されたメソッドに従って、情報をただ返しています。
最後に、これらのクラスを同じように扱えるようにコンテキストクラスを用意します。
最後にコンテキストクラスの用意
上記で用意した具体的な実装を持つクラス(果物クラス)を抽象的に扱うことができるように、コンテキストクラスを実装します。
<?php
namespace Fruits;
require_once('./Interfaces/FruitInterface.php');
use Interfaces\FruitInterface;
class Fruit {
private $fruit;
public function __construct(FruitInterface $fruit)
{
$this->fruit = $fruit;
}
public function execute()
{
$params = [];
$params['name'] = $this->fruit->getName();
$params['color'] = $this->fruit->getColor();
$params['has_like'] = $this->fruit->getHasLike();
$params['order_of_popularity'] = $this->fruit->getOrderOfPopularity();
return $params;
}
}
コンストラクタに渡ってくるインスタンスを、FruitInterfaceの型で縛り、渡ってくるインスタンスがFruitInterfaceをimplementsしていることを保証します。
実際に上記クラスを使ってブラウザに表示する部分の処理
これで、インターフェースとそれを実装する具象クラス、具象クラスを抽象的に扱うコンテキストクラスが用意されました。
これらを最後駆使して、ブラウザに情報を表示していきます。
まずはコード全体
<?php
require_once('./Fruits/Fruit.php');
require_once('./Fruits/Orange.php');
require_once('./Fruits/Apple.php');
require_once('./Fruits/Grape.php');
require_once('./Fruits/NoneFruit.php');
use Fruits\Fruit;
use Fruits\Orange;
use Fruits\Apple;
use Fruits\Grape;
use Fruits\NoneFruit;
$fruitName = getFruitNameByUrlPath();
$fruitData = getFruitData($fruitName);
function getFruitNameByUrlPath(){
if(!isset($_GET['fruit'])){
return 'none';
}
return $_GET['fruit'];
}
function getFruitData($fruitName){
$fruit = getFruitInstance($fruitName);
$fruitContext = new Fruit($fruit);
return $fruitContext->execute();
}
function getFruitInstance($fruitName) {
switch ($fruitName) {
case 'orange':
return new Orange;
case 'apple':
return new Apple;
case 'grape':
return new Grape;
default:
return new NoneFruit;
}
}
?>
<?php
echo "<table border = '1'>";
echo " <tr>";
echo " <th>名前</th>";
echo " <th>色</th>";
echo " <th>好き or 嫌い</th>";
echo " <th>人気順位</th>";
echo " </tr>";
echo " <tr>";
echo " <th>$fruitData[name]</th>";
echo " <th>$fruitData[color]</th>";
echo " <th>$fruitData[has_like]</th>";
echo " <th>$fruitData[order_of_popularity]</th>";
echo " </tr>";
echo "</table>";
?>
setFruitData()で、それぞれの果物クラスをコンテキストクラスに渡して、メソッドを実行しています。
function getFruitData($fruitName){
$fruit = getFruitInstance($fruitName);
$fruitContext = new Fruit($fruit);
return $fruitContext->execute();
}
これで、以下のようにパラメーターに合わせた果物の情報の表示ができました。
余談
1つ目
ちなみに、コンテキストクラスをこのように書けば、
<?php
namespace Fruits;
require_once('./Interfaces/FruitInterface.php');
use Interfaces\FruitInterface;
class Fruit {
private $fruit;
public function __construct(FruitInterface $fruit)
{
$this->fruit = $fruit;
}
public function getName()
{
return $this->fruit->getName();
}
}
クラスを使う側では、名前の取得だけが可能です。
function getName($fruitName){
$fruit = getFruitInstance($fruitName);
$fruitContext = new Fruit($fruit);
return $fruitContext->getName();
}
2つ目
今回は、 getFruitInstance()
で、場合分けしてインスタンス生成していますが、
使いたい時にそれぞれのインスタンスを明示的に生成してあげて利用することもできます。
function getOrangeData(){
$orange = new Orange;
$fruitContext = new Fruit($orange);
return $fruitContext->execute();
}