bakaid: 20111112
今さらStateパターンの話。
某所で「Stateパターンは重いからイヤだ」ということで、
こんな感じのコードを見かけたんだけど:
class CDPlayer
def handle_event(event)
case event
when :play
handle_play
when :stop
handle_stop
end
end
def handle_play
case @state
when :stopping
start_music
@state = :playing
when :playing
pause_music
@state = :pausing
when :pausing
resume_music
@state = :playing
end
end
def handle_stop
if @state != :stopping
stop_music
@state = :stopping
end
end
def start_music; end
def pause_music; end
def resume_music; end
def stop_music; end
end
再生ボタンを二度押しするとポーズになるという仕様ね。
まぁ、イヤだといってるのにわざわざStateパターンを
使う必要もないんだけど:
class CDPlayer
def handle_event(event)
state_code = @state.handle_event(event)
case state_code
when :playing
@state = @playing
when :pausing
@state = @pausing
when :stopping
@state = @stopping
end
end
def start_music; end
def pause_music; end
def resume_music; end
def stop_music; end
end
class Playing
def handle_event(event)
case event
when :play
@player.pause_music
return :pausing
when :stop
@player.stop_music
return :stopping
end
end
end
class Pausing
def handle_event(event)
case event
when :play
@player.resume_music
return :playing
when :stop
@player.stop_music
return :stopping
end
end
end
class Stopping
def handle_event(event)
if event != :stop
@player.stop_music
end
return :stopping
end
end
かなり適当なコードで申し訳ない。
まぁ、これを見ると、確かに「Stateパターンて重いよね」
という話になるかもしんないけど。
でも、思ったんだけど、Stateパターンでは、多態を使って
switch文をなくすことが強調されるわけだけど。でも、
GoFだとContextっていうのがいるよね。あれが実はポイント
なんじゃないかと思ったんだよね。
関心の分離という話があって:
関心の分離
Stateパターンで実現したいのは何かというと、状態遷移と
いう関心事を切り離したいのが本線なんじゃないかと
思ったわけ:
class CDPlayer
def handle_event(event)
@context.handle_event(event)
end
def start_music; end
def pause_music; end
def resume_music; end
def stop_music; end
end
class CDPlayerContext
def handle_event(event)
state_code = @state.handle_event(event)
case state_code
when :playing
@state = @playing
when :pausing
@state = @pausing
when :stopping
@state = @stopping
end
end
end
この関心の分離の話は、最初のコードにもいえるわけで:
class CDPlayer
def handle_event(event)
@context.handle_event(event)
end
def start_music; end
def pause_music; end
def resume_music; end
def stop_music; end
end
class CDPlayerContext
def handle_event(event)
case event
when :play
@player.handle_play
when :stop
@player.handle_stop
end
end
def handle_play
case @state
when :stopping
@player.start_music
@state = :playing
when :playing
@player.pause_music
@state = :pausing
when :pausing
@player.resume_music
@state = :playing
end
end
def handle_stop
if @state != :stopping
@player.stop_music
@state = :stopping
end
end
end
で、「Stateパターンは重いか?」の話に戻るんだけど。
自分はそんなに重いとは思わないんだよね。クラスが
増えるのは面倒っちゃ面倒だけど。でも、個々の状態
クラスは重くなりにくいし。上のコードでいえば、音楽の
再生なんかに関する機能はCDPlayerに集まってるでしょ。
で、状態クラスが重くなるんだったら、それこそState
パターンが活きてくる。
--
ところで、上のコードで「停止状態のときに停止ボタンが
押されたらどうするの?」という話があって:
class Stopping
def handle_event(event)
if event != :stop
@player.stop_music
end
return :stopping
end
end
あ、個人的には:
class Stopping
def handle_event(event)
@player.stop_music
return :stopping
end
end
って書きたいほうなんだけど。
で、このコードだと「停止状態から停止状態に遷移する」
ってことになるんだけど。こういう感じで「ある状態から
同じ状態に遷移する」っていうのは状態遷移でよくある話で。
+------+
|aState|<--+
+------+ |
| |
+------+
こんな感じの遷移図が描かれるわけだけど。
でも、現実には、「同じ状態に遷移する」というのと、
「どこにも遷移しない」というのじゃ違うことがあるん
だよね。遷移の入り口と出口にフックを仕掛けるときとか。
「どこにも遷移しない」というのを実現するとすると:
class CDPlayerContext
def handle_event(event)
state_code = @state.handle_event(event)
case state_code
when :playing
@state = @playing
when :pausing
@state = @pausing
when :stopping
@state = @stopping
when :stay
;
end
end
end
class Stopping
def handle_event(event)
if event == :stop
return :stay
end
@player.stop_music
return :stopping
end
end
ほら、ここでContextを分離しておいた効果が出てきた。
CDPlayerをいじらずに、遷移の仕方を変えることができた。
だから、やっぱり、状態クラスを抽出するよりも、まずは
Contextを抽出したほうがいいんだよね。
Copyright © 1905 tko at jitu.org