今回からは、デザインパターンを使って、スマートにコーディングしてみようよ!ということで、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"; }
まぁこんな感じのコードを書いてみました、流れとしては
- Aに関してはWayAのパラメータで処理を分岐
- Bに関しては一旦5で割ったあまりを取得した上で、WayBのパラメータで処理を分岐
というような流れになっています。
さて、このコードの問題点は以下の点になるのではないでしょうか。
- aに関する処理とbに関する処理が単一のメソッドに集約している
- aに関する条件処理の中にアルゴリズムが含まれている
- 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・データベースにも手を出すかもしれません。
最新の投稿
- データベース2015年2月3日Symfony2 Doctrine2の小ネタ(OneToMany,ManyToOneリレーション)
- データベース2015年1月28日Symfony2 Doctrine2の小ネタ(OneToOneリレーション)
- 開発2015年1月21日Symfony2でもデザインパターン(PHPクラス編)
- 開発2014年11月26日Google検索結果画面にパンくずリストを表示する方法 (リッチスニペット対応)