Stateパターン[GoF]は状態をオブジェクトとして扱います。
よくありがちな以下のような処理(擬似コード)を見てください。
天気によって処理が異なる場合、手続き型のプログラミングでは
以下のようになるでしょう。
function 傘をさす(){
if (晴れ) { 何もしない。}
elseif(曇り){傘をさすかどうか決める。}
elseif(雨){傘をさす処理をおこなう。}
}
function 日傘をさす(){
if (晴れ) { 日傘をさす処理をおこなう。}
elseif(曇り){日傘をさすかどうか決める。}
elseif(雨){何もしない。}
}
このようにすると状態に対する分岐の数が多くなり、関数内のif分やswitch文が多くなり
それに伴って関数が巨大化していきます。そういうコードはとても読みにくいばかりでなく、
修正を加えるのが大変です。stateパターンはこのような状況の場合、リファクタリング
として非常に有効なテクニックになり得ます。
ではソース行ってみよう!
/** @auhtor kenji nagano */ class Person { Weather _weather ; public Person(Weather weather){ _weather = weather ; } public void doneUmbrella(){ _weather.doneUmbrella() ; } public void doneParasol(){ _weather.doneParasol() ; } } interface Weather { void doneUmbrella() ; void doneParasol() ; }
class Rainny implements Weather{ private String _state ; public Rainny(String state){ _state = state ; } public String toString(){ return '天気は雨です。' ; } public boolean equals(Object other){ if(other instanceof Rainny){ Rainny target = (Rainny)other ; if(target.getState().equals(_state)){ return true ; }else{ return false ; } }else{ return false ; } } public String getState(){return _state ;} public void doneUmbrella(){ System.out.println('傘をさしています。') ; } public void doneParasol() { System.out.println('日傘なんていりません。') ; }
}
class Clear implements Weather{ private String _state ; public Clear(String state){ _state = state ; }
public String toString(){ return '天気は晴れです。' ; } public boolean equals(Object other){ if(other instanceof Clear){ Clear target = (Clear)other ; if(target.getState().equals(_state)){ return true ; }else{ return false ; } }else{ return false ; } } public String getState(){return _state ;} public void doneUmbrella(){ System.out.println('傘なんていりません。') ; } public void doneParasol() { System.out.println('日傘が必要です。') ; }
} class Cloudy implements Weather{ private String _state ; Cloudy(String state){ _state = state ; } public String toString(){ return '天気は曇りです。'; } public boolean equals(Object other){ if(other instanceof Cloudy){ Cloudy target = (Cloudy)other ; if(target.getState().equals(_state)){ return true ; }else{ return false ; } }else{ return false ; } } public String getState(){return _state ;} public void doneUmbrella(){ System.out.println('たぶん傘は必要ないでしょう。') ; } public void doneParasol() { System.out.println('たぶん日傘は必要ないでしょう。') ; }
}
public class StatePtnClient{ public static void main(String[] args){ if(args.length != 1){ System.out.println('usage : java 1 or 2 or 3') ; System.exit(0) ; } Weather weather = null; Person person = null ; switch(Integer.valueOf(args[0])){ case 1 : {person = new Person(new Clear('clear')) ; break ;} case 2 : {person = new Person(new Cloudy('cloudy')) ; break ;} case 3 : {person = new Person(new Rainny('rainny')) ; break ;} default : { System.out.println('usage : java 1 or 2 or 3') ; System.exit(0) ;} } person.doneUmbrella() ; person.doneParasol() ; } }
|
■実行結果
C:\web\src\tips\GoF\State>java StatePtnClient 1
傘なんていりません。
日傘が必要です。
C:\web\src\tips\GoF\State>java StatePtnClient 2
たぶん傘は必要ないでしょう。
たぶん日傘は必要ないでしょう。
C:\web\src\tips\GoF\State>java StatePtnClient 3
傘をさしています。
日傘なんていりません。
■解説
Personクラスはコンポジション用のクラスです。 したがってWeatherインター
フェースを実装した各Stateクラス(Rainny 、Clear 、Cloudy )はPersonクラス
を介して利用します。(コンポジション)
また各StateクラスのインスタンスをWeather型に代入していることに注目して
ください!こうすることで各Stateクラスの実装をクライアントは意識することが
ありません。(多相)
これもオブジェクト指向プログラミングの基本です。
■考察
Stateパターンは、リファクタリングの際に最も用いられるパターンの一つです。
関連するパターンとしてStrategyパターン[GoF]があります。
Stateパターンが状態をオブジェクトとして扱うのに対し、Strategyパターンは
ロジック(処理)をオブジェクトとして扱います。
if分やswitch文が巨大になっているようであれば、Stateパターンを利用して
リファクタリングしましょう。また同じロジックが重複しているならばStrategy
パターンを利用しましょう。
両パターンの特徴としてどちらもコンポジションを使っていることがあげられます。
等連載で何度も問題として取り上げていますが、コンポジションはオブジェクト同士
の結合を緩やかにしてくれます。それによって例えばStrategyパターンであれば
新しいロジックに変更することが容易になります。
(クライアント側に変更が発生しない。)