Menu
Coddy logo textTech

ステートパターン

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

チャレンジ

簡単

信号機を完成させてください。状態 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
quiz icon腕試し

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

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

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