Menu
CoddyTech

ステートパターン

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
challenge icon

チャレンジ

簡単

信号機を完成させましょう。状態である 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
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rubyオンラインコンパイラ