ステートパターン
CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 49/57。
state パターンは、オブジェクトの現在の状態に依存する振る舞いを、個別の状態オブジェクトに移します。メインオブジェクトであるコンテキストは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(赤は緑、緑は黄、黄は赤に遷移)、can_go?(緑の場合のみ)および to_s(色)があります。TrafficLight は赤で開始し、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オンラインコンパイラ