Menu
Coddy logo textTech

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

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