35
35

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 5 years have passed since last update.

ニジボックスAdvent Calendar 2015

Day 10

デザインパターンのススメ

Last updated at Posted at 2015-12-10

#はじめに
ここで紹介するデザインパターンは以下の2つです。

  • Factoryパターン
  • Strategyパターン

いろいろデザインパターンはありますが、この2つの組み合わせが個人的に好きなのでこの2つについて書いていきます。
実際に使ってみての肌感ベース+説明しやすさを優先しているので「これはこうだろ」「関数作ればいいじゃん」とかある実装内容です。
組み合わせた実装例だけ見たい人は一番下まですっ飛ばしてください。
#デザインパターンのメリット、デメリット
シングルトンとかは使っておいた方がいいと思いますが、この2つは必ず使わなければならいというものではないので状況に応じて使い分けましょう。
####メリット

  • Factoryパターン
  • 呼び出すクラスを意識する必要が無くなる
  • Strategyパターン
  • メソッドの実行順序を保証してくれる
  • 処理の追加・削除・切り替えが容易
  • 役割の切り分けによりソースが整理され見やすくなる(個人差あり)

####デメリット

  • Factoryパターン
  • 機能を増やす度にFactoryクラスのメンテが必要
  • Strategyパターン
  • 開発初期から導入していないと既存に影響する部分の導入コストが高い

言葉だけで書くとなんのこっちゃって感じなのでここから例も交えて!

#Factoryパターンとは
Factoryという言葉の通り工場です。
「工場に対して発注書を作り依頼を出し納品物を受け取る」という単純な作りになります。

実装例

Factory.php
<?php

define('CALC_ADD', 1);
define('CALC_SUB', 2);

class CalculatorFactory {
    public function createCalculator($mode) {
         if ($mode == CALC_ADD) {
            $calculator = new AddCalculatior();
        } else if ($mode == CALC_SUB) {
            $calculator = new SubCalculatior();
        }
        return $calculator;
    }
}

$factory = new CalculatorFactory();
$calculator = $factory->createCalculator(CALC_ADD);

やっている事は至極単純でパラメータによって呼び出すクラスを切り替えているだけです。このFactoryクラスを使う実装者にとっては

  • 工場(CalculatorFactory)に対して
  • 発注書($mode)を作り
  • 依頼(createCalculator())を出し
  • 納品物($calculator)を受け取る

だけであって実際に計算機(AddCalculatior、SubCalculatior)作成の過程を意識しなくてもよくなります。
この例だと単純すぎてここまでする意味があまりないですが、計算機作成の過程が複雑化してくると効果が出てきます。

#Strategyパターンとは
これもStrategyという言葉の通り戦略です。
このパターンは処理の順序などの戦略を保証してくれます。
「この計算機はこういう順序で動かすようにしよう」というものです。

実装例

Strategy.php
<?php

define('CALC_ADD', 1);
define('CALC_SUB', 2);

class CalculatorStrategy {
    private $_calculator;

    function __construct($calculator) {
        $this->_calculator = $calculator;
    }

    public function calc($num1, $num2) {
        $calculator = $this->_calculator;
        $calculator->setNum($num1, $num2);
        $calculator->calculation();
        $result = $calculator->getResult();
        return $result;
    }
}

$strategy = new CalculatorStrategy($calculator);
echo $strategy->calc(3, 2);

こちらもやっている事は単純で

  • setNum()で値をセット
  • calculation()で計算
  • getResult()で値を取得

を順番に行っているだけです。

この処理を呼び出すことでどの計算機でも同じ処理順序で実行することができます。
ただし、加算機と減算器の開発担当者がそれぞれ好きな関数名をつけてしまうとCalcurationStrategyでは処理を行うことができなくなってしまいます。

ここで必要になってくるのがInterfaceです。
Interfaceで必要な関数を定義することによってCalcurationStrategyで処理の保証がされます。
今回の例ではfactoryパターンで使用していた計算機のベースになる定義を行っています。

インターフェース

CalculatiorInterface.php
interface CalculatiorInterface {
    public function setNum($num1, $num2);
    public function calculation();
    public function getResult();
}

インターフェースを実装した加算機

AddCalculatior.php
class AddCalculatior implements CalculatiorInterface {
    private $_num1;
    private $_num2;
    private $_result;
    public function setNum($num1, $num2) {
        $this->_num1 = $num1;
        $this->_num2 = $num2;
    }
    public function calculation() {
        $this->_result = $this->_num1 + $this->_num2;
    }
    public function getResult() {
        return $this->_result;
    }
}

インターフェースを実装した減算機

SubCalculatior.php
class SubCalculatior implements CalculatiorInterface {
    private $_num1;
    private $_num2;
    private $_result;
    public function setNum($num1, $num2) {
        $this->_num1 = $num1;
        $this->_num2 = $num2;
    }
    public function calculation() {
        $this->_result = $this->_num1 - $this->_num2;
    }
    public function getResult() {
        return $this->_result;
    }
}

今回の例の場合setNum()、getResult()やメンバ変数は共通なので、親クラスを作ってそちらに持たせてしまう方法もあります。
ただし、親クラスに持たせたものに修正が入ると影響範囲が広くなってしまうため、よく検討した上で行いましょう。
上でちょろっと書いたシングルトンなどは親クラスに仕込んでしまってもいいかもしれません。
Javaと違ってオーバーライドできないのでどこまでやってしまっていいかは難しいですね。

#ファクトリパターン、ストラテジパターンを組み合わせる
さて前置きが長くなりましたがここからが本題です。
内容的には上記の例を組み合わせただけなのでここまでを理解出来てる人には簡単だと思います。

実装例

Factory_Strategy.php
<?php

define('CALC_ADD', 1);
define('CALC_SUB', 2);

class CalculatorStrategy {
    private $_calculator;

    function __construct($calculator) {
        $this->_calculator = $calculator;
    }

    public function calc($num1, $num2) {
        $calculator = $this->_calculator;
        $calculator->setNum($num1, $num2);
        $calculator->calculation();
        $result = $calculator->getResult();
        return $result;
    }
}

class CalculatorFactory {
    public function createCalculator($mode) {
         if ($mode == CALC_ADD) {
            $calculator = new AddCalculatior();
        } else if ($mode == CALC_SUB) {
            $calculator = new SubCalculatior();
        }
        return $calculator;
    }
}

interface CalculatiorInterface {
    public function setNum($num1, $num2);
    public function calculation();
    public function getResult();
}

class AddCalculatior implements CalculatiorInterface {
    private $_num1;
    private $_num2;
    private $_result;
    public function setNum($num1, $num2) {
        $this->_num1 = $num1;
        $this->_num2 = $num2;
    }
    public function calculation() {
        $this->_result = $this->_num1 + $this->_num2;
    }
    public function getResult() {
        return $this->_result;
    }
}

class SubCalculatior implements CalculatiorInterface {
    private $_num1;
    private $_num2;
    private $_result;
    public function setNum($num1, $num2) {
        $this->_num1 = $num1;
        $this->_num2 = $num2;
    }
    public function calculation() {
        $this->_result = $this->_num1 - $this->_num2;
    }
    public function getResult() {
        return $this->_result;
    }
}

$factory = new CalculatorFactory();
$calculator = $factory->createCalculator(CALC_ADD);

$strategy = new CalculatorStrategy($calculator);
echo $strategy->calc(3, 2);

このようにFactoryパターンを使って作成したInterfaceで定義された計算機をStrategyパターンを用いて処理を行うことで、呼び出し元では末尾の4ステップを実行するだけでよくなり、関数の実装を気にすることなく処理を行うことが可能となります。
変更を加えたい場合も

  • 計算機を追加、削除したい場合は「Factoryクラスの振り分け修正、計算機の修正」
  • 計算機の処理を変えたい場合は「Strategyクラスの処理順序修正、計算機の修正」

と切り分けが出来ており修正する箇所の明確になります。
また、ClassやInterfaceをファイルで分けられるので、すっきりとしたソースにすることができます。

#おまけ
こんな感じに2つをくっつけてシーン管理クラスみたいに使うのもありっちゃありです。
というか個人で何か作る時はよくやります。

よくやる実装
class CalculatorManager {
    private $_calculator;

    public function createCalculator($mode) {
         if ($mode == CALC_ADD) {
            $calculator = new AddCalculatior();
        } else if ($mode == CALC_SUB) {
            $calculator = new SubCalculatior();
        }
        $this->_calculator = calculator;
    }

    public function calc($num1, $num2) {
        $calculator = $this->_calculator;
        $calculator->setNum($num1, $num2);
        $calculator->calculation();
        $result = $calculator->getResult();
        return $result;
    }
}

#最後に
ここまでダラダラと書いてきましたが、実はこのパターンに名前があるのを知ったのは最近だったりします。
なんとなくいつも作っていたものが最後のおまけに載せてあるようなロジックなので、Strategyなのかどうかも若干あやふやでした。
改めて勉強してそれとなく落とし込めたと思います。

Factoryパターンの説明ではありましたが組み合わせた例だとInterfaceで定義しているのでAbstructFactoryパターンとなりそうな気がしました。

あまりデザインパターンに囚われすぎるのも柔軟な開発ができなくなりそうなので、ちょっとしたエッセンス程度に覚えておくのが一番かもしれませんね!

35
35
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
35
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?