コラム

Strategy ストラテジーパターンで戦略的にクラスを使用する!?

※この記事の注意事項
・できるだけ初心者の方が理解しやすいように、細かい説明は省略しております。
・最後まで読み進められるように、多少ユーモアを交えて表現していることがあります。

========================================

Strategy ストラテジーパターンで戦略的にクラスを使用する!?

戦略的?ってどういうこと?と思いますよね。

例えば、ビジネスにおいて「ここは絶対こういう風にしたい」などの要望があるとします。それこそが戦略です。つまり、意思のある行為みたいな感じです。

具体的に言うと、
「CSVファイルを読み込む機能を付けて欲しい」
「でも、場合によってはTSVファイルが来る場合もある、、、」
「イレギュラーなんだけど、年に一回はXMLファイルを読み込みたい」

などの要望がある場合です。。。

運用側で統一してくれよ、、、と思いますが、ビジネスですので文句は言わず、黙って実装します。

始めから型が変わることがわかっているので、型ごとにクラスを用意します。
・CSVファイルインポートクラス
・TSVファイルインポートクラス
・XMLファイルインポートクラス

共通する部分は親クラスを用意して、ファイル名のセットや、ファイルの表示などを実装します。
親クラスを用意することでクラスの型を統一することができます。

ImportStrategyという親クラスに対して、それぞれのサブクラスを組んだ場合、
class CsvImportStrategy extends ImportStrategy
class TsvImportStrategy extends ImportStrategy
class XmlImportStrategy extends ImportStrategy
のようになりますので、サブクラスとしては別々でも、すべてImportStrategyクラスの型として統一できます。この特性を利用します。

親クラスと3つのサブクラスを用意しました。

/**
 * データを取り込む抽象(abstract)クラス
 */
abstract class ImportStrategy
{
    private $filename;

    //初期化時にファイル名を読み込む
    public function __construct($filename)
    {
        $this->filename = $filename;
    }

    private function getFilename()
    {
        return $this->filename;
    }

    //データの取得は定義しているけど、実際の読み込みはサブクラスでそれぞれ定義する
    public function getData()
    {
        if (is_dir($this->getFilename()) || !is_readable($this->getFilename())) {
            throw new Exception('This file is NotReadable.');
        }

        return $this->readData($this->getFilename());
    }

    abstract public function readData($filename);
}

/**
 * CSVファイルを読み込むサブクラス
 */
class CsvImportStrategy extends ImportStrategy
{
    //サブクラスでコンストラクタを設定していないので、抽象クラスのコンストラクタが実行される

    public function readData($filename)
    {
        //CSVファイルを読み込んで配列にする処理を書く
        $file = file_get_contents($filename);

        //結果として配列を返す
        return $file;
    }
}

/**
 * TSVファイルを読み込むサブクラス
 */
class TsvImportStrategy extends ImportStrategy
{
    //サブクラスでコンストラクタを設定していないので、抽象クラスのコンストラクタが実行される

    public function readData($filename)
    {
        //TSVファイルを読み込んで配列にする処理を書く
        $file = file_get_contents($filename);

        //結果として配列を返す
        return $file;
    }
}

/**
 * XMLファイルを読み込むサブクラス
 */
class XmlImportStrategy extends ImportStrategy
{
    //サブクラスでコンストラクタを設定していないので、抽象クラスのコンストラクタが実行される

    public function readData($filename)
    {
        //XMLファイルを読み込んで配列にする処理を書く
        $file = file_get_contents($filename);

        //結果として配列を返す
        return $file;
    }
}

親クラスで、コンストラクタでのファイル名の取得、ファイル名のセット、データの表示などを実装しています。

サブクラスでは純粋にファイルの読み込みだけを行っています。

Contextクラスという文脈に応じて機能するクラスを用意します。
どのサブクラスが来ても親クラスの機能を使えるようにしたものです。ただ、全然関係ないクラスでは困るので、親クラスの型を持っていることが判定基準になります。
これをタイプヒンティングで判定します。

以下にコンテキストクラスを用意しました。

/**
 * 文脈(Context)に合わせてクラスを読み込むクラス
 */
class ImportContext
{
    private $strategy;

    //ImportStrategy型のクラスであれば受け入れる
    public function __construct(ImportStrategy $strategy)
    {
        $this->strategy = $strategy;
    }

    public function getContextData()
    {
        return $this->strategy->getData();
    }
}

9行目のコンストラクタで親クラスの型を持っているか判定しています。
これによりサブクラスとしては型が違っても、親クラスの型を継承していれば使用できることになります。

getContextDataでデータの表示を行っています。これは親のメソッドを呼び出して利用できる形にしています。

実際の利用方法は以下になります。

$strategy1 = new CsvImportStrategy('./test.csv');
$context1 = new ImportContext($strategy1);
$data1 = $context1->getContextData();
var_dump($data1);

$strategy2 = new TsvImportStrategy('./test.tsv');
$context2 = new ImportContext($strategy2);
$data2 = $context2->getContextData();
var_dump($data2);

$strategy3 = new XmlImportStrategy('./test.xml');
$context3 = new ImportContext($strategy3);
$data3 = $context3->getContextData();
var_dump($data3);

このサンプルではサブクラスの実装で、CSVファイルの読み込みなどは実装していませんが、単純にファイルの中身を読み込んで表示しています。
このサンプルと同じ階層に、test.csv test.tsv test.xmlの3つのファイルを用意して、ファイルの中身にそれぞれ文章を入れると、その中身が表示されます。

このStrategy ストラテジーパターンの良いところは、クラスごとに処理がまとまっていることと、サブクラスが変わってもそこにif文などの条件判定が必要ないことです。
Contextクラスを用意することで、動的に切り替えを行うことができています。

========================================

▼PHPのオブジェクト指向とデザインパターン記事一覧

関連記事

コメント

  • トラックバックは利用できません。

  • コメント (0)

  1. この記事へのコメントはありません。