概要
ケースごとに異なる処理をしたい時に、
長くなりがちな条件判定を書くのではなく、状態を持たせてその状態に合った処理を呼び出すやり方。
多数の言語に応用が効くと思う。
環境
Vagrant上で管理している、いつものローカル開発環境です。
php 5.3.3
Centos6.7(bento)
使いそうなシチュエーション。
登録、検索フォームなどに入力された値の判定、加工など
項目ごとに処理が微妙に異なったり、対象項目の変更が行われる可能性が高いケース。
判定条件自体は単純で、ケースが多い時に、特に有効かも。
ケース別の判定を、条件分岐で書くのが望ましくない理由
この手の分岐処理は、ifやswitchなどの条件分岐で記述するケースも多いですが・・・見苦しくなりやすい。
switch に至っては、「PHP switch」で検索すると1ページ目に「使ってはいけない」なんてタイトルのページがヒットします。
・・・確かに、使ってはいけない。
少なくとも Rubyの case〜whenよとは使い勝手が悪い。
判定処理が長くなり、読みにくくなりやすい
これが最大かつ致命的です。
分岐と言うとifやswitchなどを使うことが多いものの、行数が長くなりやすく可読性や保守性が低くなりがち。
/* 与えられた条件別に、何がしかの応答をする*/
if ( $type == 'numeric' ){
numericProcess();
}else if ( $type == 'string' ){
stringProcess();
}(中略)
}else{
noType();
}
/* 与えられた条件別に、何がしかの応答をする*/
switch($type)
case 'numeric':
numericProcess();
break;
case 'numeric':
numericProcess();
break;
(中略)
default:
noType();
break;
こんな感じの判定が延々と長く続き、画面を埋め尽くしている光景を想像してみてください・・・。
こんな長ったらしいコード、僕修正したくないよ! やり直し!!(T野御大風に)
とか言いつつ、私もこんなコードを書いていて、後日修正するときにうんざりしたことがあります。
さらに、この手の分岐処理はプログラム全体ではあちこちで使い、呼び出している処理は共通・・・なんてケースが多い。
その都度こんな処理を書くと・・・地雷の大量生産。
(PHPの場合)switchの条件分岐が「緩やかな比較」のため、誤処理を起こしやすい
PHP 型の比較表
http://php.net/manual/ja/types.comparisons.php#types.comparisions-loose
緩やかな比較の場合、NULLと""を同じものとして判定しているなど、これまた地雷になりそうな悪寒。
そこで・・・
ケースごとの状態を保存し、対応するクラスのメソッドを呼び出すやり方を採用する
ステートパターンやストラテジーパターンなどについて調べながらコードを書いたけど、下のリストの右側のような意味なんだろう(まだ消化しきれてない)。
オブジェクト : 対象物、ここでは状態別処理が必要なものとする
ステートパターン : オブジェクトの状態を表すデザインパターン
ストラテジーパターン : 状態に応じたアルゴリズムを選択するデザインパターン
オブジェクトが取る処理を、保存している状態に応じて表せば良い。
ごく単純な、状態に応じて異なる処理を呼び出す場合のサンプル
各クラスの違いが紛らわしくて見苦しいけど、
・各条件に対応する処理を、別クラスに実装
・それぞれを異なるオブジェクト、クラスで呼び出す。
<?php
/*特定条件に対応した処理パターンその1*/
class State1 {
function echoStatus(){
echo 'isState1';
}
}
/*特定条件に対応した処理パターンその2*/
class State2 {
function echoStatus(){
echo 'isState2';
}
}
/*状態を指定し、処理クラスを呼び出すクラス。その1*/
class User {
/*ステータスを指定する変数を確保*/
private $my_status;
/*ステータス指定変数に、状態にあったオブジェクトを追加するメソッド*/
function set_status($status){
$this->my_status = new $status;
}
/*状態にあったオブジェクトを呼び出し、処理を行うメソッド*/
function echoState(){
$this->my_status->echoStatus();
}
}
/*状態を指定し、処理クラスを呼び出すクラス。その2*/
class User2 {
/*ステータスを指定する変数を確保*/
private $my_status;
/*ステータス指定変数に、状態にあったオブジェクトを追加するメソッド*/
function set_status($status){
$this->my_status = new $status;
}
/*状態にあったオブジェクトを呼び出し、処理を行うメソッド。Userと異なりecho文が含まれる*/
function echoState(){
echo ' User2 ';
$this->my_status->echoStatus();
}
}
/*処理一覧*/
$us = new User();
$us->set_status('State1');
$us->echoState();
$us->set_status('State2');
$us->echoState();
$us2 = new User2();
$us2->set_status('State1');
$us2->echoState();
$us2->set_status('State2');
$us2->echoState();
?>
isState1isState2 User2 isState1 User2 isState2
処理内容は単純なメッセージを出力するだけの、いい加減な内容ですが
Userなどの状態に応じた処理を行いたいクラスのインスタンスにおいて、
状態指定メソッドを呼び出す際に、指定したい状態に対応する処理が実装されているクラス名を引数にすることによって、対応する処理のクラスのインスタンスを生成し、クラス変数に保存する。
状態指定後(対応するクラスのインスタンス生成後)、指定された状態に応じた処理を呼び出すメソッドにおいて、対応する処理が呼び出されている。
ことはご理解いただけたかと思います。
どのくらい読みやすくなるか?
コードの行数に着目すると、ifやswitchなどの分岐処理の場合
1分岐辺り3行くらい。分岐数×3 削減できます(笑)
もっとも、単純な行数削減以上にこの時にはこのメソッドを呼び出す。
といった処理を動的に書けるし、エディタ内の1画面に収まる点でも読みやすい。
上記ソースコードの問題点
状態を指定するたびに、new でクラスの新規作成をしているため、このプログラムが動作しているサーバマシンに対し与える負荷が大きい。
そして、実際の使用環境で使いそうな「判定に応じた文字整形処理」みたいなものは、いちいちインスタンスを生成しなくとも良さそうなケースが多いので、シングルトン(静的)なクラスでも良さそう。
参考リンク
[JavaScript] とっても長いswitch/case文 :ウンコード・マニア
http://unkode-mania.net/view/5018ddaa7e19b40f5a000002
あくまで、極端な例ですが・・・。延々と続く判定処理にうんざり。
しかも、後で変更する場合も改修しにくい。
PHP :: switch の case 文で厳密な型比較をする
http://tm.root-n.com/programming:php:etc:strict_switch_case
緩い方変換したくない時には、上記のような対応でも可能だが、
長くて良いにくいコードの解決にはならない。
PHPマニュアルのswich構文の説明
http://php.net/manual/ja/control-structures.switch.php
国定の構文を使う時に疑問を感じたら、まずは公式を参考に。
switch文を使ってはいけない。
http://blog.tojiru.net/article/403698034.html
ポリモーフィズムを使った方が良いって話。