Состояние

описание
Состояние — поведенческий шаблон проектирования. Используется в тех случаях, когда во время выполнения программы объект должен менять своё поведение в зависимости от своего состояния.

Шаблон позволяет менять поведение класса при изменении состояния.
Когда использовать паттерн
  • Когда объект может находиться в нескольких различных состояниях, и его поведение зависит от текущего состояния.

  • Когда слишком много условных операторов (if, elif, switch) для выбора действий на основании текущего состояния.

  • Когда необходимо добавить новые состояния, не изменяя существующий код.
Основные участники паттерна
State (Состояние) — интерфейс, определяющий общие методы для всех конкретных состояний.

ConcreteState (Конкретное состояние) — классы, реализующие поведение для каждого конкретного состояния.

Context (Контекст) — класс, который хранит текущее состояние и делегирует ему поведение.
Пример реализации паттерна
Рассмотрим пример автоматического турникета, который может находиться в двух состояниях: закрыто (по умолчанию) и открыто. Турникет открывается после оплаты, а затем автоматически закрывается. Поведение турникета будет меняться в зависимости от его состояния: если он закрыт, требуется оплата для открытия, а если открыт, можно пройти, и затем он закроется.
Шаг 1: Определим интерфейс состояния
from abc import ABC, abstractmethod

class GateState(ABC):
    @abstractmethod
    def insert_coin(self, gate):
        pass

    @abstractmethod
    def pass_through(self, gate):
        pass
Здесь GateState — это абстрактный класс, представляющий интерфейс состояния турникета. Он определяет два метода: insert_coin для оплаты и pass_through для прохода через турникет. Каждый метод будет реализован конкретными состояниями.
Шаг 2: Реализация конкретных состояний
class ClosedState(GateState):
    def insert_coin(self, gate):
        print("Coin inserted. Gate is opening.")
        gate.set_state(OpenState())
    
    def pass_through(self, gate):
        print("Gate is closed. Please insert a coin.")

class OpenState(GateState):
    def insert_coin(self, gate):
        print("Gate is already open. Please pass through.")
    
    def pass_through(self, gate):
        print("You passed through the gate. Gate is closing.")
        gate.set_state(ClosedState())
Создадим два класса, представляющие конкретные состояния турникета: закрытое и открытое состояние.

Здесь:
  • ClosedState и OpenState реализуют методы insert_coin и pass_through по-своему.
  • В ClosedState вставка монеты переводит турникет в состояние открытого (OpenState), а попытка пройти вызывает предупреждение.
  • В OpenState вставка монеты не требуется, так как турникет уже открыт, и после прохода он возвращается в закрытое состояние.
Шаг 3: Создаём контекст (турникет)
class Gate:
    def __init__(self):
        self.state = ClosedState()  # Турникет по умолчанию закрыт

    def set_state(self, state):
        self.state = state

    def insert_coin(self):
        self.state.insert_coin(self)

    def pass_through(self):
        self.state.pass_through(self)
Контекст — это класс, который управляет состоянием объекта и позволяет переключаться между состояниями.

В классе Gate:
  • Метод set_state позволяет изменять текущее состояние турникета.
  • Методы insert_coin и pass_through делегируют работу текущему состоянию.
Шаг 4: Использование паттерна
def main():
    gate = Gate()

    # Пытаемся пройти через закрытый турникет
    gate.pass_through()  # Gate is closed. Please insert a coin.

    # Вставляем монету, турникет открывается
    gate.insert_coin()  # Coin inserted. Gate is opening.

    # Пытаемся вставить монету снова, пока турникет открыт
    gate.insert_coin()  # Gate is already open. Please pass through.

    # Проходим через турникет, он закрывается
    gate.pass_through()  # You passed through the gate. Gate is closing.

    # Повторим попытку пройти через закрытый турникет
    gate.pass_through()  # Gate is closed. Please insert a coin.

main()
Результат выполнения
Gate is closed. Please insert a coin.
Coin inserted. Gate is opening.
Gate is already open. Please pass through.
You passed through the gate. Gate is closing.
Gate is closed. Please insert a coin.
Как работает код?
  • Класс Gate управляет состоянием, начиная с закрытого состояния (ClosedState).
  • Методы insert_coin и pass_through вызывают соответствующие действия в зависимости от текущего состояния турникета.
  • При смене состояния турникет изменяет своё поведение, не изменяя самого объекта Gate.
Преимущества паттерна
  • Упрощение кода. Паттерн устраняет множество условных операторов, поскольку переключение логики происходит внутри классов состояния.

  • Гибкость. Новые состояния можно добавлять легко, не изменяя код контекста.

  • Поддерживаемость. Каждый класс состояния отвечает только за своё поведение, что упрощает тестирование и модификацию.
Недостатки
  • Усложнение структуры. Необходимость создания отдельных классов для каждого состояния может увеличивать сложность кода.

  • Дополнительные затраты. Переключение между состояниями может привести к увеличению потребления памяти и времени выполнения, если состояний много.