Symfony2でもデザインパターン(PHPクラス編)

今回からは、デザインパターンを使って、スマートにコーディングしてみようよ!ということで、Symfony2でも美しいコードを書いてみようということです。

今回は、PHPのクラスを用いて記載するので、Symfony2限定ということではありません、次回以降Symfony2の機能をフルパワーで発揮したデザインパターンに関して書きたいと思っています。

デザインパターンに関しては
http://www.nulab.co.jp/designPatterns/designPatterns1/designPatterns1-1.html
あたりを見るとよいです、リンク先はJavaで記載されいますが参考にはなります。要するに、オブジェクト指向の素晴らしい点をパターン化していると思ってくれればよいです。

以下のコードを具体例として挙げてみましょう

<?php
class LongMethod{
    
    public $resA;
    
    public $resB;
    
    /**
     * very complex long method
     */
    public function method($a, $b, $wayA, $wayB){
        switch ($wayA) {
            case "sqrt" :
                $this->resA = $a ^ 2;
                break;
            case "plus" :
                $this->resA = $a + 10;
                break;
            case "minus" :
                $this->resA = $a - 10;
                break;
            default :
                $this->resA = $a;
        }
        
        $b %= 5;
        switch ($wayB) {
            case "sqrt" :
                $this->resB = $b ^ 2;
                break;
            case "plus" :
                $this->resB = $b + 10;
                break;
            case "minus" :
                $this->resB = $b - 10;
                break;
            default :
                $this->resB = $b;
        }
    }
}

$datas = Array(
    Array("a" => 1, "b" => 2, "wayA" => "minus", "wayB" => "sqrt" ),
    Array("a" => 3, "b" => 4, "wayA" => "plus", "wayB" => "minus" ),
    Array("a" => 5, "b" => 6, "wayA" => "", "wayB" => "plus" ),
    Array("a" => 7, "b" => 8, "wayA" => "sqrt", "wayB" => "" )
);

foreach ($datas as $data) {
    $methodClass = new LongMethod();
    $methodClass->method($data["a"], $data["b"], $data["wayA"], $data["wayB"]);
    // print -9, 13, 5, 49
    echo "resA - {$methodClass->resA}\n";
    // print 4, -6, 16, 8
    echo "resB - {$methodClass->resB}\n";
}

まぁこんな感じのコードを書いてみました、流れとしては

  1. Aに関してはWayAのパラメータで処理を分岐
  2. Bに関しては一旦5で割ったあまりを取得した上で、WayBのパラメータで処理を分岐

というような流れになっています。
さて、このコードの問題点は以下の点になるのではないでしょうか。

  1. aに関する処理とbに関する処理が単一のメソッドに集約している
  2. aに関する条件処理の中にアルゴリズムが含まれている
  3. bに関する条件処理の中にアルゴリズムが含まれている

デザインパターンはこのような問題点を解決するためにあります。
まずは問題点1を解決してみましょう

<?php
class MiddleMethod{
    
    public $resA;
    
    public $resB;
    
    /**
     * method A
     * @param unknown $a
     * @param unknown $wayA
     */
    public function methodA ($a, $wayA) {
        switch ($wayA) {
            case "sqrt" :
                $this->resA = $a ^ 2;
                break;
            case "plus" :
                $this->resA = $a + 10;
                break;
            case "minus" :
                $this->resA = $a - 10;
                break;
            default :
                $this->resA = $a;
        }
    }
    
    /**
     * method B
     * @param unknown $b
     * @param unknown $wayB
     */
    public function methodB ($b, $wayB) {
        $b %= 5;
        switch ($wayB) {
            case "sqrt" :
                $this->resB = $b ^ 2;
                break;
            case "plus" :
                $this->resB = $b + 10;
                break;
            case "minus" :
                $this->resB = $b - 10;
                break;
            default :
                $this->resB = $b;
        }
    }
    
    /**
     * very complex long method
     */
    public function method($a, $b, $wayA, $wayB){
        methodA($a, $wayA);
        
        methodB($b, $wayB);
    }
}

はい、methodの中をaの処理とbの処理に分離してみました。

次に、問題点2を解決していきましょう、ここからオブジェクト指向をフル活用していきます

ストラテジパターン

<?php
Interface methodAInterface {
    public function methodA($a);
}

class methodASqrt implements methodAInterface {
    public function methodA ($a) {
        return $a ^ 2;
    }
}

class methodAPlus implements methodAInterface {
    public function methodA ($a) {
        return 10 + $a;
    }
}

class methodAMinus implements methodAInterface {
    public function methodA ($a) {
        return $a - 10;
    }
}

class methodADefault implements methodAInterface {
    public function methodA ($a) {
        return $a;
    }
}

class ShortAMethod{
    
    public $resA;
    
    public $resB;
    
    /**
     * method A
     * @param unknown $a
     * @param unknown $wayA
     */
    public function methodA ($a, $wayA) {
        $methodAClass;
        switch ($wayA) {
            case "sqrt" :
                $methodAClass = new methodASqrt();
                break;
            case "plus" :
                $methodAClass = new methodAPlus();
                break;
            case "minus" :
                $methodAClass = new methodAMinus();
                break;
            default :
                $methodAClass = new methodADefault();
        }
        $this->resA = $methodAClass->methodA($a);
    }
    
    /**
     * method B
     * @param unknown $b
     * @param unknown $wayB
     */
    public function methodB ($b, $wayB) {
        $b %= 5;
        switch ($wayB) {
            case "sqrt" :
                $this->resB = $b ^ 2;
                break;
            case "plus" :
                $this->resB = $b + 10;
                break;
            case "minus" :
                $this->resB = $b - 10;
                break;
            default :
                $this->resB = $b;
        }
    }
    
    /**
     * very short method
     */
    public function method($a, $b, $wayA, $wayB){
        methodA($a, $wayA);
        
        methodB($b, $wayB);
    }
}

はい、これで、条件分岐とそれぞれの処理を分離させることができました。ここからさらに、クラス作成を分離するファクトリパターンというものも実装すればなおよいでしょう。

一応、このパターンの有用性を述べておくと、今回でいえば、Switch分のCaseの数が増加した場合の対処がしやすくなるという点です。

では、続いて問題点3を解決してみましょう、bに関する処理は、分岐する前に共通の処理が存在しています

テンプレートメソッド

<?php
Interface methodAInterface {
    public function methodA($a);
}

class methodASqrt implements methodAInterface {
    public function methodA ($a) {
        return $a ^ 2;
    }
}

class methodAPlus implements methodAInterface {
    public function methodA ($a) {
        return 10 + $a;
    }
}

class methodAMinus implements methodAInterface {
    public function methodA ($a) {
        return $a - 10;
    }
}

class methodADefault implements methodAInterface {
    public function methodA ($a) {
        return $a;
    }
}

abstract class methodBAbstract {
    
    public function methodB($b) {
        $this->commonMethod($b);
        return $this->diffMethod($b);
    }
    
    protected function commonMethod(&$b) {
        $b %= 5;
    }
    
    abstract protected function diffMethod($b);
    
}

class methodBSqrt extends methodBAbstract {
    protected function diffMethod ($b) {
        return $b ^ 2;
    }
}

class methodBPlus extends methodBAbstract {
    protected function diffMethod ($b) {
        return $b + 10;
    }
}

class methodBMinus extends methodBAbstract {
    protected function diffMethod ($b) {
        return $b - 10;
    }
}

class methodBDefault extends methodBAbstract {
    protected function diffMethod ($b) {
        return $b;
    }
}

class ShortABMethod{
    
    public $resA;
    
    public $resB;
    
    /**
     * method A
     * @param unknown $a
     * @param unknown $wayA
     */
    public function methodA ($a, $wayA) {
        $methodAClass;
        switch ($wayA) {
            case "sqrt" :
                $methodAClass = new methodASqrt();
                break;
            case "plus" :
                $methodAClass = new methodAPlus();
                break;
            case "minus" :
                $methodAClass = new methodAMinus();
                break;
            default :
                $methodAClass = new methodADefault();
        }
        $this->resA = $methodAClass->methodA($a);
    }
    
    /**
     * method B
     * @param unknown $b
     * @param unknown $wayB
     */
    public function methodB ($b, $wayB) {
        $methodBClass;
        switch ($wayB) {
            case "sqrt" :
                $methodBClass = new methodBSqrt();
                break;
            case "plus" :
                $methodBClass = new methodBPlus();
                break;
            case "minus" :
                $methodBClass = new methodBMinus();
                break;
            default :
                $methodBClass = new methodBDefault();
        }
        $this->resB = $methodBClass->methodB($b);
    }
    
    /**
     * very short method
     */
    public function method($a, $b, $wayA, $wayB){
        methodA($a, $wayA);
        
        methodB($b, $wayB);
    }
}

Abstractでクラスを発行することで、共通のメソッドを維持したまま、処理を分岐することができます。

もちろん、実際には、各クラス・インターフェースは実際には別ファイルに保存すべきです。

今回は以上となります。

投稿者プロフィール

開発 アルバイト
中の人には主に、
PHP・Symfony2系の人と
Ruby・Rails系の人がいます。
ときどきJavascript・データベースにも手を出すかもしれません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Time limit is exhausted. Please reload CAPTCHA.