ステートパターン
CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 49/57。
状態パターンは、オブジェクトの現在の状態に依存する振る舞いを、個別の状態オブジェクトに移します。メインオブジェクトであるコンテキストは1つの状態を保持し、呼び出しをその状態に転送します。状態は応答し、次にどの状態へ移るかを決定します。
class Red
def next_state
Green.new
end
def to_s
"red"
end
end
class Green
def next_state
Yellow.new
end
def to_s
"green"
end
end
class Yellow
def next_state
Red.new
end
def to_s
"yellow"
end
end
class TrafficLight
def initialize
@state = Red.new
end
def change
@state = @state.next_state
self
end
def to_s
"light is #{@state}"
end
end
light = TrafficLight.new
puts light
puts light.change
puts light.change.change出力:
light is red
light is green
light is redパターンを使わない場合、コンテキストの各メソッドでステータス値に対する case が必要になり、新しい状態を追加するたびに、それらすべてを編集することになります。状態オブジェクトを使うと、各状態は独自のルールを持つ1つのクラスになります:
# パターンを使わない場合
def can_go?
case @status
when :red then false
when :green then true
when :yellow then false
end
end
# パターンを使う場合
def can_go?
@state.can_go?
end状態は、適合しないイベントを拒否できます。ここでは、ドラフトを直接公開することはできず、まずレビューを通過する必要があります。
class Draft
def submit
Review.new
end
def publish
raise ArgumentError, "cannot publish a draft"
end
def to_s
"draft"
end
end
class Review
def publish
Published.new
end
def to_s
"in review"
end
end
class Published
def to_s
"published"
end
end
state = Draft.new
begin
state.publish
rescue ArgumentError => e
puts e.message
end
state = state.submit
state = state.publish
puts state出力:
cannot publish a draft
published共有の基底クラスを使えば、すべての状態に、たとえばすべてのイベントを拒否するようなデフォルトの応答を設定できます。これにより、各状態では受け付けるイベントだけを定義すればよくなります。
class State
def submit
raise ArgumentError, "cannot submit #{self}"
end
def approve
raise ArgumentError, "cannot approve #{self}"
end
end
class Review < State
def approve
Published.new
end
endチャレンジ
簡単信号機を完成させましょう。状態である Red、Green、Yellow には、それぞれ next_state(red は green に、green は yellow に、yellow は red に移行する)、can_go?(green のみ)および to_s(色)があります。TrafficLight は red で開始し、change は次の状態に移行し、status は状態に問い合わせて red: stop または green: go を返します。
提供されたコードは change または status コマンドを読み取り、各コマンドの後に状態を出力します。
コードは red.rb、green.rb、yellow.rb、traffic_light.rb に記述します。main.rb には提供された入出力コードがあり、編集できません。
自分で試してみよう
require_relative 'red'
require_relative 'green'
require_relative 'yellow'
require_relative 'traffic_light'
# 用意された入出力コード: そのままにしてください
input = []
while (line = gets)
input << line.chomp
end
light = TrafficLight.new
input.each do |cmd|
light.change if cmd == "change"
puts light.status
end
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rubyオンラインコンパイラ