8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【デザインパターン】PHPでStrategyパターンの実装をしてみた

Last updated at Posted at 2020-01-06

環境

  • PHP 7.2.18

はじめに

今更ながら、デザインパターンを真面目に理解し始めようとしている2年目エンジニアであります。
まずは事始めに「Strategyパターン」から勉強していこうと思い、この記事を書きました。
普通はJavaで勉強すべきだと思うのですが、Java初心者すぎるので、一番触っているPHPで勉強しています。
ちなみに、会社のブログでも同じような記事を書いたので、ここに貼っておきます。
レガシーコードをぶっ壊す!〜基本のキ編〜

Strategyパターンとは

Strategyとは、「戦略」や「作戦」という意味を持ちます。
この「戦略」や「作戦」毎にクラスを分割して、簡単にそれらを切り替えることができるのが特徴です。
共通のインターフェースを定義し、クラスを使う側(以下クライアント)はそれに伴った処理を実行するので、クライアントは具体的な実装に依存しなくなります。
strategyパターンのクラス図は以下のようなものです。
スクリーンショット 2020-01-07 1.19.10.png

Strategyパターンは何がいいか

自分が一番Strategyパターンを使うことで受けたメリットは、if文やswitch文などの複雑な条件分岐をスマートにすることです。
普段であれば場合分けしなければならないところを、「戦略」をクラスに格納することによりコードが非常にシンプルになり可読性や拡張性が上がります。

例題の概要

今回は、例として簡単なプログラムを作りました。
URLのクエリパラメーターから、果物の名前を取得して、その果物の情報を表示しています。
イメージ図とクラス図です。

イメージ図

スクリーンショット 2020-01-07 1.20.28.png

クラス図

スクリーンショット 2020-01-07 1.18.39.png

ファイル構成

スクリーンショット 2020-01-06 23.56.21.png

ソースコード

実装

まずはインターフェースの定義

まずはじめに、果物のクラスがどんな実装をしているべきなのかを定義します。
今回は、「名前」「色」「好きか嫌いか」「人気順位」をブラウザに表示するので、それぞれの情報を取得するメソッドを定義します。こんな感じです。

FruitInterface.php

<?php

namespace Interfaces;
interface FruitInterface{
    public function getName();
    public function getColor();
    public function getHasLike();
    public function getOrderOfPopularity();
}

この後は、このインターフェースに従って具体的な実装を持つクラス(果物クラス)を実装していきます。

次は具体的な実装を持つクラス(果物クラス)の用意

先ほど定義したインターフェースに従って、それぞれの果物クラスを実装していきます。
例として、OrangeクラスとAppleクラスを記載しておきます。

Orange.php

<?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;
    }

}

Apple.php
<?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;
    }

}

どちらも定義されたメソッドに従って、情報をただ返しています。
最後に、これらのクラスを同じように扱えるようにコンテキストクラスを用意します。

最後にコンテキストクラスの用意

上記で用意した具体的な実装を持つクラス(果物クラス)を抽象的に扱うことができるように、コンテキストクラスを実装します。

Fruit.php

<?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していることを保証します。

実際に上記クラスを使ってブラウザに表示する部分の処理

これで、インターフェースとそれを実装する具象クラス、具象クラスを抽象的に扱うコンテキストクラスが用意されました。
これらを最後駆使して、ブラウザに情報を表示していきます。
まずはコード全体

index.php
<?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()で、それぞれの果物クラスをコンテキストクラスに渡して、メソッドを実行しています。

index.php
function getFruitData($fruitName){
    $fruit = getFruitInstance($fruitName);
    $fruitContext = new Fruit($fruit);
    return $fruitContext->execute();
}

これで、以下のようにパラメーターに合わせた果物の情報の表示ができました。

スクリーンショット 2020-01-07 0.25.29.png

余談

1つ目

ちなみに、コンテキストクラスをこのように書けば、

Fruit.php

<?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();
    }
}

クラスを使う側では、名前の取得だけが可能です。

index.php

function getName($fruitName){
    $fruit = getFruitInstance($fruitName);
    $fruitContext = new Fruit($fruit);
    return $fruitContext->getName();
}

2つ目

今回は、 getFruitInstance() で、場合分けしてインスタンス生成していますが、
使いたい時にそれぞれのインスタンスを明示的に生成してあげて利用することもできます。

index.php
function getOrangeData(){
    $orange = new Orange;
    $fruitContext = new Fruit($orange);
    return $fruitContext->execute();
}
8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?