Menu
CoddyTech

Паттерн «Состояние»

Часть раздела Объектно-ориентированное программирование путешествия по Python на Coddy. Урок 53 из 64.

Паттерн «Состояние» позволяет объекту изменять своё поведение при изменении его внутреннего состояния. Кажется, что класс объекта меняется в зависимости от его текущего состояния.

Вот простые классы состояний для светофора:

class RedState:
    def next_state(self, light):
        print("Red -> Green")
        light.state = GreenState()
    
    def current_color(self):
        return "Red"

class GreenState:
    def next_state(self, light):
        print("Green -> Yellow")
        light.state = YellowState()
    
    def current_color(self):
        return "Green"

class YellowState:
    def next_state(self, light):
        print("Yellow -> Red")
        light.state = RedState()
    
    def current_color(self):
        return "Yellow"

Каждое состояние определяет, что происходит при переходе к следующему состоянию.

Создай класс контекста, который хранит текущее состояние:

class TrafficLight:
    def __init__(self):
        self.state = RedState()  # Начать с красного
    
    def change(self):
        self.state.next_state(self)
    
    def get_color(self):
        return self.state.current_color()

Светофор делегирует поведение своему текущему объекту состояния.

Используйте светофор:

light = TrafficLight()
print(f"Current: {light.get_color()}")

light.change()  # Красный -> зелёный
print(f"Current: {light.get_color()}")

light.change()  # Зелёный -> жёлтый
print(f"Current: {light.get_color()}")

light.change()  # Жёлтый -> красный
print(f"Current: {light.get_color()}")

Создай ещё один пример с простым плеером:

class PlayingState:
    def play(self, player):
        print("Already playing")
    
    def stop(self, player):
        print("Stopping music")
        player.state = StoppedState()

class StoppedState:
    def play(self, player):
        print("Starting music")
        player.state = PlayingState()
    
    def stop(self, player):
        print("Already stopped")

class MusicPlayer:
    def __init__(self):
        self.state = StoppedState()
    
    def play(self):
        self.state.play(self)
    
    def stop(self):
        self.state.stop(self)

player = MusicPlayer()
player.play()   # Запуск музыки
player.play()   # Музыка уже воспроизводится
player.stop()   # Остановка музыки
player.stop()   # Музыка уже остановлена

Вывод:

Current: Red
Red -> Green
Current: Green
Green -> Yellow
Current: Yellow
Yellow -> Red
Current: Red
Starting music
Already playing
Stopping music
Already stopped

Ключевой момент: паттерн «Состояние» инкапсулирует поведение, специфичное для каждого состояния, в отдельных классах и позволяет объекту контекста делегировать задачи текущему состоянию. Когда состояние меняется, поведение меняется автоматически. Это избавляет от сложных операторов if/else и упрощает добавление новых состояний.

challenge icon

Задание

Легко

Реализуй паттерн «Состояние» в state.py для лампы с одной кнопкой. Каждое нажатие переключает лампу между тремя состояниями по циклу: выключена, слабое свечение, яркое свечение, а затем снова выключена. Вместо цепочки if с проверкой строки каждое состояние представлено собственным объектом, который знает, что происходит при нажатии кнопки.

  • LampState — базовый класс с методами press(lamp) и describe().
  • OffState, LowState и HighState реализуют эти методы: press вызывает lamp.set_state(...), передавая следующее состояние (из выключенного в состояние слабого свечения, из слабого свечения — в яркое, из яркого — в выключенное), а describe возвращает Lamp is off, Lamp is on low или Lamp is on high.
  • Lamp — контекст: он начинает работу в состоянии OffState, set_state заменяет объект состояния, press_button делегирует действие текущему состоянию, а status возвращает текущее описание.

driver.py заблокирован: он нажимает кнопку и выводит статус. Обрати внимание: Lamp никогда не проверяет, в каком состоянии находится; это решают объекты состояний.

Попробуйте сами

from state import Lamp

# Обработчик тестового случая (не изменять)
test_case = input()
lamp = Lamp()

if test_case == "initial_test":
    print(lamp.status())
elif test_case == "one_press_test":
    lamp.press_button()
    print(lamp.status())
elif test_case == "cycle_test":
    for _ in range(4):
        print(lamp.status())
        lamp.press_button()
elif test_case == "many_presses_test":
    for _ in range(7):
        lamp.press_button()
    print(lamp.status())
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Python