Menu
Coddy logo textTech

Wzorzec stanu

Część sekcji Programowanie obiektowe ścieżki Ruby w Coddy. Lekcja 49 z 57.

Wzorzec stanu przenosi zachowanie zależne od bieżącego stanu obiektu do osobnych obiektów stanu. Główny obiekt, kontekst, przechowuje jeden stan i przekazuje do niego wywołania; stan odpowiada i decyduje, który stan będzie następny:

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

Wynik:

light is red
light is green
light is red

Bez tego wzorca każda metoda kontekstu wymaga instrukcji case sprawdzającej wartość statusu, a dodanie nowego stanu oznacza konieczność edycji ich wszystkich. W przypadku obiektów stanów każdy stan jest klasą zawierającą własne reguły:

# bez wzorca
def can_go?
  case @status
  when :red then false
  when :green then true
  when :yellow then false
  end
end

# z wzorcem
def can_go?
  @state.can_go?
end

Stany mogą odrzucić zdarzenie, które do nich nie pasuje. Tutaj szkicu nie można opublikować bezpośrednio; najpierw musi przejść weryfikację:

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

Wynik:

cannot publish a draft
published

Wspólna klasa bazowa może zapewnić każdemu stanowi domyślną odpowiedź, na przykład odrzucanie każdego zdarzenia, dzięki czemu każdy stan definiuje tylko te zdarzenia, które akceptuje:

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

Wyzwanie

Łatwy

Uzupełnij sygnalizację świetlną. Stany Red, Green i Yellow mają każdy next_state (czerwony przechodzi w zielony, zielony w żółty, a żółty w czerwony), can_go? (tylko zielony) i to_s (kolor). TrafficLight zaczyna od czerwonego, change przechodzi do następnego stanu, a status zwraca red: stop lub green: go, pytając o stan.

Dostarczony kod odczytuje polecenia change lub status i wyświetla status po każdym poleceniu.

Umieść swój kod w plikach red.rb, green.rb, yellow.rb i traffic_light.rb. main.rb zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

require_relative 'red'
require_relative 'green'
require_relative 'yellow'
require_relative 'traffic_light'

# Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Ruby online