Menu
Coddy logo textTech

Шаблон состояния

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 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