Menu
CoddyTech

状态模式

本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 49 课,共 57 课。

状态模式会将依赖对象当前状态的行为移到单独的状态对象中。主对象上下文持有一个状态,并将调用转发给它;状态会响应调用,并决定下一个状态是什么:

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,而新增状态就意味着要修改所有这些方法。使用状态对象时,每个状态都是一个包含自身规则的类:

# 不使用该模式
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 在线编译器