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

Проблема
Паттерн Состояние невозможно рассматривать в отрыве от концепции машины состояний, также известной как стейт-машина или конечный автомат.

Конечный автомат.
Основная идея в том, что программа может находиться в одном из нескольких состояний, которые всё время сменяют друг друга. Набор этих состояний, а также переходов между ними, предопределён и конечен. Находясь в разных состояниях, программа может по-разному реагировать на одни и те же события, которые происходят с ней.
Такой подход можно применить и к отдельным объектам. Например, объект Документ может принимать три состояния: Черновик, Модерация или Опубликован. В каждом из этих состоянии метод опубликовать будет работать по-разному:
- Из черновика он отправит документ на модерацию.
- Из модерации — в публикацию, но при условии, что это сделал администратор.
- В опубликованном состоянии метод не будет делать ничего.

Возможные состояния документа и переходы между ними.
Машину состояний чаще всего реализуют с помощью множества условных операторов, if либо switch, которые проверяют текущее состояние объекта и выполняют соответствующее поведение. Наверняка вы уже реализовали хотя бы одну машину состояний в своей жизни, даже не зная об этом. Как насчёт вот такого кода, выглядит знакомо?
Основная проблема такой машины состояний проявится в том случае, если в Документ добавить ещё десяток состояний. Каждый метод будет состоять из увесистого условного оператора, перебирающего доступные состояния. Такой код крайне сложно поддерживать. Малейшее изменение логики переходов заставит вас перепроверять работу всех методов, которые содержат условные операторы машины состояний.
Путаница и нагромождение условий особенно сильно проявляется в старых проектах. Набор возможных состояний бывает трудно предопределить заранее, поэтому они всё время добавляются в процессе эволюции программы. Из-за этого решение, которое выглядело простым и эффективным в самом начале разработки, может впоследствии стать проекцией большого макаронного монстра.
Решение
Паттерн Состояние предлагает создать отдельные классы для каждого состояния, в котором может пребывать объект, а затем вынести туда поведения, соответствующие этим состояниям.
Вместо того, чтобы хранить код всех состояний, первоначальный объект, называемый контекстом, будет содержать ссылку на один из объектов-состояний и делегировать ему работу, зависящую от состояния.

Документ делегирует работу своему активному объекту-состоянию.
Благодаря тому, что объекты состояний будут иметь общий интерфейс, контекст сможет делегировать работу состоянию, не привязываясь к его классу. Поведение контекста можно будет изменить в любой момент, подключив к нему другой объект-состояние.
Очень важным нюансом, отличающим этот паттерн от Стратегии, является то, что и контекст, и сами конкретные состояния могут знать друг о друге и инициировать переходы от одного состояния к другому.
Аналогия из жизни
Ваш смартфон ведёт себя по-разному, в зависимости от текущего состояния:
- Когда телефон разблокирован, нажатие кнопок телефона приводит к каким-то действиям.
- Когда телефон заблокирован, нажатие кнопок приводит к экрану разблокировки.
- Когда телефон разряжен, нажатие кнопок приводит к экрану зарядки.
Структура

- Контекст хранит ссылку на объект состояния и делегирует ему часть работы, зависящей от состояний. Контекст работает с этим объектом через общий интерфейс состояний. Контекст должен иметь метод для присваивания ему нового объекта-состояния.
- Состояние описывает общий интерфейс для всех конкретных состояний.
- Конкретные состояния реализуют поведения, связанные с определённым состоянием контекста. Иногда приходится создавать целые иерархии классов состояний, чтобы обобщить дублирующий код.
Состояние может иметь обратную ссылку на объект контекста. Через неё не только удобно получать из контекста нужную информацию, но и осуществлять смену его состояния. - И контекст, и объекты конкретных состояний могут решать, когда и какое следующее состояние будет выбрано. Чтобы переключить состояние, нужно подать другой объект-состояние в контекст.
Псевдокод
В этом примере паттерн Состояние изменяет функциональность одних и тех же элементов управления музыкальным проигрывателем, в зависимости от того, в каком состоянии находится сейчас проигрыватель.

Пример изменение поведения проигрывателя с помощью состояний.
Объект проигрывателя содержит объект-состояние, которому и делегирует основную работу. Изменяя состояния, можно менять то, как ведут себя элементы управления проигрывателя.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
Применимость
Когда у вас есть объект, поведение которого кардинально меняется в зависимости от внутреннего состояния, причём типов состояний много, и их код часто меняется.
Паттерн предлагает выделить в собственные классы все поля и методы, связанные с определёнными состояниями. Первоначальный объект будет постоянно ссылаться на один из объектов-состояний, делегируя ему часть своей работы. Для изменения состояния в контекст достаточно будет подставить другой объект-состояние.
Когда код класса содержит множество больших, похожих друг на друга, условных операторов, которые выбирают поведения в зависимости от текущих значений полей класса.
Паттерн предлагает переместить каждую ветку такого условного оператора в собственный класс. Тут же можно поселить и все поля, связанные с данным состоянием.
Когда вы сознательно используете табличную машину состояний, построенную на условных операторах, но вынуждены мириться с дублированием кода для похожих состояний и переходов.
Паттерн Состояние позволяет реализовать иерархическую машину состояний, базирующуюся на наследовании. Вы можете отнаследовать похожие состояния от одного родительского класса и вынести туда весь дублирующий код.
Шаги реализации
- Определитесь с классом, который будет играть роль контекста. Это может быть как существующий класс, в котором уже есть зависимость от состояния, так и новый класс, если код состояний размазан по нескольким классам.
- Создайте общий интерфейс состояний. Он должен описывать методы, общие для всех состояний, обнаруженных в контексте. Заметьте, что не всё поведение контекста нужно переносить в состояние, а только то, которое зависит от состояний.
- Для каждого фактического состояния создайте класс, реализующий интерфейс состояния. Переместите код, связанный с конкретными состояниями в нужные классы. В конце концов, все методы интерфейса состояния должны быть реализованы во всех классах состояний.
При переносе поведения из контекста вы можете столкнуться с тем, что это поведение зависит от приватных полей или методов контекста, к которым нет доступа из объекта состояния. Существует парочка способов обойти эту проблему.
Самый простой — оставить поведение внутри контекста, вызывая его из объекта состояния. С другой стороны, вы можете сделать классы состояний вложенными в класс контекста, и тогда они получат доступ ко всем приватным частям контекста. Но последний способ доступен только в некоторых языках программирования (например, Java, C#). - Создайте в контексте поле для хранения объектов-состояний, а также публичный метод для изменения значения этого поля.
- Старые методы контекста, в которых находился зависимый от состояния код, замените на вызовы соответствующих методов объекта-состояния.
- В зависимости от бизнес-логики, разместите код, который переключает состояние контекста либо внутри контекста, либо внутри классов конкретных состояний.
Преимущества и недостатки
Преимущества
- Избавляет от множества больших условных операторов машины состояний.
- Концентрирует в одном месте код, связанный с определённым состоянием.
- Упрощает код контекста.
Недостатки
- Может неоправданно усложнить код, если состояний мало и они редко меняются.
Примеры реализации паттерна
Python
- Сложность: 1/3
- Популярность: 2/3
- Применимость: Паттерн Состояние часто используют в Python для превращения в объекты громоздких стейт-машин, построенных на операторах
switch. - Признаки применения паттерна: Методы класса делегируют работу одному вложенному объекту.
Этот пример показывает структуру паттерна Состояние, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
Context: Transition to ConcreteStateA
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA
PHP
- Сложность: 1/3
- Популярность: 1/3
- Применимость: Паттерн Состояние иногда используют в PHP для превращения громоздких стейт-машин, построенных на операторах
switch, в объекты. - Признаки применения паттерна: Методы класса делегируют работу одному вложенному объекту.
Этот пример показывает структуру паттерна Состояние, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
Context: Transition to RefactoringGuru\State\Conceptual\
ConcreteStateA.
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to RefactoringGuru\State\Conceptual\
ConcreteStateB.
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to RefactoringGuru\State\Conceptual\
ConcreteStateA.
Задачи
Задача 1. Светофор
Реализуйте светофор как конечный автомат с состояниями Red, Yellow, Green. Каждое состояние знает, в какое следующее переходить. Контекст TrafficLight делегирует переключение активному состоянию. Добавьте таймер: каждое состояние хранит длительность (в секундах).
from __future__ import annotations
from abc import ABC, abstractmethod
class TrafficLightState(ABC):
def __init__(self, light: "TrafficLight") -> None:
self._light = light
@property
@abstractmethod
def color(self) -> str: ...
@property
@abstractmethod
def duration(self) -> int: ...
@abstractmethod
def next(self) -> None:
"""Перейти в следующее состояние."""
...
class RedState(TrafficLightState):
@property
def color(self) -> str:
return "RED"
@property
def duration(self) -> int:
return 60
def next(self) -> None:
# TODO: перейти в GreenState
pass
class YellowState(TrafficLightState):
@property
def color(self) -> str:
return "YELLOW"
@property
def duration(self) -> int:
return 5
def next(self) -> None:
# TODO: перейти в RedState
pass
class GreenState(TrafficLightState):
@property
def color(self) -> str:
return "GREEN"
@property
def duration(self) -> int:
return 45
def next(self) -> None:
# TODO: перейти в YellowState
pass
class TrafficLight:
def __init__(self) -> None:
# TODO: начальное состояние — Red
self._state: TrafficLightState | None = None
def change_state(self, state: TrafficLightState) -> None:
self._state = state
print(f"[TrafficLight] → {state.color} ({state.duration}s)")
def tick(self) -> None:
"""Переключить на следующее состояние."""
# TODO: делегировать текущему состоянию
pass
@property
def current_color(self) -> str:
return self._state.color
# Ожидаемый результат:
# light = TrafficLight()
# # [TrafficLight] → RED (60s)
# light.tick()
# # [TrafficLight] → GREEN (45s)
# light.tick()
# # [TrafficLight] → YELLOW (5s)
# light.tick()
# # [TrafficLight] → RED (60s)
# print(light.current_color) # RED
<?php
abstract class TrafficLightState
{
public function __construct(protected TrafficLight $light) {}
abstract public function getColor(): string;
abstract public function getDuration(): int;
abstract public function next(): void;
}
class RedState extends TrafficLightState
{
public function getColor(): string { return "RED"; }
public function getDuration(): int { return 60; }
public function next(): void
{
// TODO: перейти в GreenState
}
}
class YellowState extends TrafficLightState
{
public function getColor(): string { return "YELLOW"; }
public function getDuration(): int { return 5; }
public function next(): void
{
// TODO: перейти в RedState
}
}
class GreenState extends TrafficLightState
{
public function getColor(): string { return "GREEN"; }
public function getDuration(): int { return 45; }
public function next(): void
{
// TODO: перейти в YellowState
}
}
class TrafficLight
{
private ?TrafficLightState $state = null;
public function __construct()
{
// TODO: начальное состояние — Red
}
public function changeState(TrafficLightState $state): void
{
$this->state = $state;
echo "[TrafficLight] → {$state->getColor()} ({$state->getDuration()}s)\n";
}
public function tick(): void
{
// TODO: делегировать текущему состоянию
}
public function getCurrentColor(): string
{
return $this->state->getColor();
}
}
// Ожидаемый результат:
// $light = new TrafficLight();
// // [TrafficLight] → RED (60s)
// $light->tick();
// // [TrafficLight] → GREEN (45s)
// $light->tick();
// // [TrafficLight] → YELLOW (5s)
// $light->tick();
// // [TrafficLight] → RED (60s)
// echo $light->getCurrentColor(); // RED
Задача 2. Жизненный цикл заказа в интернет-магазине
Реализуйте систему управления заказом с состояниями: New → Paid → Shipped → Delivered → Completed. Из любого состояния возможен переход в Cancelled (кроме Delivered и Completed). Каждое состояние разрешает или запрещает определённые действия.
from __future__ import annotations
from abc import ABC, abstractmethod
class OrderState(ABC):
def __init__(self, order: "Order") -> None:
self._order = order
@property
@abstractmethod
def name(self) -> str: ...
def pay(self) -> None:
print(f"[{self.name}] Действие недоступно в текущем состоянии")
def ship(self) -> None:
print(f"[{self.name}] Действие недоступно в текущем состоянии")
def deliver(self) -> None:
print(f"[{self.name}] Действие недоступно в текущем состоянии")
def complete(self) -> None:
print(f"[{self.name}] Действие недоступно в текущем состоянии")
def cancel(self) -> None:
print(f"[{self.name}] Действие недоступно в текущем состоянии")
class NewState(OrderState):
@property
def name(self) -> str:
return "New"
def pay(self) -> None:
# TODO: перейти в PaidState
pass
def cancel(self) -> None:
# TODO: перейти в CancelledState
pass
class PaidState(OrderState):
@property
def name(self) -> str:
return "Paid"
def ship(self) -> None:
# TODO: перейти в ShippedState
pass
def cancel(self) -> None:
# TODO: перейти в CancelledState (возврат средств)
pass
class ShippedState(OrderState):
@property
def name(self) -> str:
return "Shipped"
def deliver(self) -> None:
# TODO: перейти в DeliveredState
pass
def cancel(self) -> None:
# TODO: перейти в CancelledState
pass
class DeliveredState(OrderState):
@property
def name(self) -> str:
return "Delivered"
def complete(self) -> None:
# TODO: перейти в CompletedState
pass
# cancel недоступен из Delivered
class CompletedState(OrderState):
@property
def name(self) -> str:
return "Completed"
# Терминальное состояние — все действия недоступны
class CancelledState(OrderState):
@property
def name(self) -> str:
return "Cancelled"
# Терминальное состояние
class Order:
def __init__(self, order_id: str) -> None:
self.order_id = order_id
self._state: OrderState = NewState(self)
print(f"Order {order_id} создан. Статус: {self._state.name}")
def change_state(self, state: OrderState) -> None:
self._state = state
print(f"Order {self.order_id} → {state.name}")
def pay(self) -> None: self._state.pay()
def ship(self) -> None: self._state.ship()
def deliver(self) -> None: self._state.deliver()
def complete(self) -> None: self._state.complete()
def cancel(self) -> None: self._state.cancel()
@property
def status(self) -> str:
return self._state.name
# Ожидаемый результат:
# order = Order("ORD-001")
# order.ship() # [New] Действие недоступно
# order.pay() # Order ORD-001 → Paid
# order.ship() # Order ORD-001 → Shipped
# order.deliver() # Order ORD-001 → Delivered
# order.cancel() # [Delivered] Действие недоступно
# order.complete() # Order ORD-001 → Completed
# print(order.status) # Completed
<?php
abstract class OrderState
{
public function __construct(protected Order $order) {}
abstract public function getName(): string;
public function pay(): void { echo "[{$this->getName()}] Действие недоступно\n"; }
public function ship(): void { echo "[{$this->getName()}] Действие недоступно\n"; }
public function deliver(): void { echo "[{$this->getName()}] Действие недоступно\n"; }
public function complete(): void { echo "[{$this->getName()}] Действие недоступно\n"; }
public function cancel(): void { echo "[{$this->getName()}] Действие недоступно\n"; }
}
class NewState extends OrderState
{
public function getName(): string { return "New"; }
public function pay(): void { /* TODO: → PaidState */ }
public function cancel(): void { /* TODO: → CancelledState */ }
}
class PaidState extends OrderState
{
public function getName(): string { return "Paid"; }
public function ship(): void { /* TODO: → ShippedState */ }
public function cancel(): void { /* TODO: → CancelledState */ }
}
class ShippedState extends OrderState
{
public function getName(): string { return "Shipped"; }
public function deliver(): void { /* TODO: → DeliveredState */ }
public function cancel(): void { /* TODO: → CancelledState */ }
}
class DeliveredState extends OrderState
{
public function getName(): string { return "Delivered"; }
public function complete(): void { /* TODO: → CompletedState */ }
}
class CompletedState extends OrderState
{
public function getName(): string { return "Completed"; }
}
class CancelledState extends OrderState
{
public function getName(): string { return "Cancelled"; }
}
class Order
{
private OrderState $state;
public function __construct(private readonly string $orderId)
{
$this->state = new NewState($this);
echo "Order $orderId создан. Статус: {$this->state->getName()}\n";
}
public function changeState(OrderState $state): void
{
$this->state = $state;
echo "Order {$this->orderId} → {$state->getName()}\n";
}
public function pay(): void { $this->state->pay(); }
public function ship(): void { $this->state->ship(); }
public function deliver(): void { $this->state->deliver(); }
public function complete(): void { $this->state->complete(); }
public function cancel(): void { $this->state->cancel(); }
public function getStatus(): string { return $this->state->getName(); }
}
// Ожидаемый результат:
// $order = new Order("ORD-001");
// $order->ship(); // [New] Действие недоступно
// $order->pay(); // Order ORD-001 → Paid
// $order->ship(); // Order ORD-001 → Shipped
// $order->deliver(); // Order ORD-001 → Delivered
// $order->cancel(); // [Delivered] Действие недоступно
// $order->complete(); // Order ORD-001 → Completed
// echo $order->getStatus(); // Completed
Задача 3. Банкомат (ATM)
Реализуйте банкомат с состояниями: Idle (ожидание карты), CardInserted (карта вставлена, ожидание PIN), Authenticated (PIN введён, выбор операции), DispensingCash (выдача наличных). Неверный PIN трижды — переход в CardBlocked.
from __future__ import annotations
from abc import ABC, abstractmethod
class ATMState(ABC):
def __init__(self, atm: "ATM") -> None:
self._atm = atm
@abstractmethod
def insert_card(self, card_number: str) -> None: ...
@abstractmethod
def enter_pin(self, pin: str) -> None: ...
@abstractmethod
def select_amount(self, amount: float) -> None: ...
@abstractmethod
def eject_card(self) -> None: ...
class IdleState(ATMState):
def insert_card(self, card_number: str) -> None:
# TODO: сохранить card_number в ATM, перейти в CardInsertedState
pass
def enter_pin(self, pin: str) -> None:
print("[Idle] Сначала вставьте карту")
def select_amount(self, amount: float) -> None:
print("[Idle] Сначала вставьте карту")
def eject_card(self) -> None:
print("[Idle] Карта не вставлена")
class CardInsertedState(ATMState):
def insert_card(self, card_number: str) -> None:
print("[CardInserted] Карта уже вставлена")
def enter_pin(self, pin: str) -> None:
# TODO: проверить PIN через atm.verify_pin(pin),
# при успехе → AuthenticatedState,
# при 3 неудачах → CardBlockedState
pass
def select_amount(self, amount: float) -> None:
print("[CardInserted] Сначала введите PIN")
def eject_card(self) -> None:
# TODO: вернуть карту, → IdleState
pass
class AuthenticatedState(ATMState):
def insert_card(self, card_number: str) -> None:
print("[Authenticated] Сессия уже активна")
def enter_pin(self, pin: str) -> None:
print("[Authenticated] PIN уже введён")
def select_amount(self, amount: float) -> None:
# TODO: проверить баланс, перейти в DispensingCashState
pass
def eject_card(self) -> None:
# TODO: завершить сессию, → IdleState
pass
class DispensingCashState(ATMState):
def insert_card(self, card_number: str) -> None:
print("[Dispensing] Операция выполняется")
def enter_pin(self, pin: str) -> None:
print("[Dispensing] Операция выполняется")
def select_amount(self, amount: float) -> None:
print("[Dispensing] Операция выполняется")
def eject_card(self) -> None:
# TODO: выдать наличные, списать с баланса, → IdleState
pass
class CardBlockedState(ATMState):
def insert_card(self, card_number: str) -> None:
print("[Blocked] Карта заблокирована")
def enter_pin(self, pin: str) -> None:
print("[Blocked] Карта заблокирована")
def select_amount(self, amount: float) -> None:
print("[Blocked] Карта заблокирована")
def eject_card(self) -> None:
# TODO: вернуть карту (заблокированную), → IdleState
pass
class ATM:
def __init__(self, balance: float = 10000.0) -> None:
self._balance = balance
self._card_number: str | None = None
self._pin_attempts = 0
self._requested_amount = 0.0
self._state: ATMState = IdleState(self)
# Фиктивная БД: card_number → PIN
self._cards = {"1234-5678": "1234", "8765-4321": "0000"}
self._accounts = {"1234-5678": 5000.0, "8765-4321": 300.0}
def change_state(self, state: ATMState) -> None:
self._state = state
def verify_pin(self, pin: str) -> bool:
return self._cards.get(self._card_number) == pin
def get_account_balance(self) -> float:
return self._accounts.get(self._card_number, 0.0)
def deduct(self, amount: float) -> None:
self._accounts[self._card_number] -= amount
self._balance -= amount
# Публичный интерфейс
def insert_card(self, card_number: str) -> None:
self._state.insert_card(card_number)
def enter_pin(self, pin: str) -> None:
self._state.enter_pin(pin)
def select_amount(self, amount: float) -> None:
self._state.select_amount(amount)
def eject_card(self) -> None:
self._state.eject_card()
# Ожидаемый результат:
# atm = ATM()
# atm.insert_card("1234-5678") # Карта принята
# atm.enter_pin("9999") # Неверный PIN (попытка 1/3)
# atm.enter_pin("9999") # Неверный PIN (попытка 2/3)
# atm.enter_pin("1234") # Успешная аутентификация
# atm.select_amount(500.0) # Запрос 500.0
# atm.eject_card() # Выдано 500.0, карта возвращена
<?php
abstract class ATMState
{
public function __construct(protected ATM $atm) {}
abstract public function insertCard(string $cardNumber): void;
abstract public function enterPin(string $pin): void;
abstract public function selectAmount(float $amount): void;
abstract public function ejectCard(): void;
}
class IdleState extends ATMState
{
public function insertCard(string $cardNumber): void
{ /* TODO: сохранить карту, → CardInsertedState */ }
public function enterPin(string $pin): void
{ echo "[Idle] Сначала вставьте карту\n"; }
public function selectAmount(float $amount): void
{ echo "[Idle] Сначала вставьте карту\n"; }
public function ejectCard(): void
{ echo "[Idle] Карта не вставлена\n"; }
}
class CardInsertedState extends ATMState
{
public function insertCard(string $cardNumber): void
{ echo "[CardInserted] Карта уже вставлена\n"; }
public function enterPin(string $pin): void
{ /* TODO: проверить PIN, при 3 неудачах → CardBlockedState */ }
public function selectAmount(float $amount): void
{ echo "[CardInserted] Сначала введите PIN\n"; }
public function ejectCard(): void
{ /* TODO: вернуть карту, → IdleState */ }
}
class AuthenticatedState extends ATMState
{
public function insertCard(string $cardNumber): void
{ echo "[Authenticated] Сессия уже активна\n"; }
public function enterPin(string $pin): void
{ echo "[Authenticated] PIN уже введён\n"; }
public function selectAmount(float $amount): void
{ /* TODO: проверить баланс, → DispensingCashState */ }
public function ejectCard(): void
{ /* TODO: завершить сессию, → IdleState */ }
}
class DispensingCashState extends ATMState
{
public function insertCard(string $cardNumber): void { echo "[Dispensing] Операция выполняется\n"; }
public function enterPin(string $pin): void { echo "[Dispensing] Операция выполняется\n"; }
public function selectAmount(float $amount): void { echo "[Dispensing] Операция выполняется\n"; }
public function ejectCard(): void
{ /* TODO: выдать наличные, списать с баланса, → IdleState */ }
}
class CardBlockedState extends ATMState
{
public function insertCard(string $cardNumber): void { echo "[Blocked] Карта заблокирована\n"; }
public function enterPin(string $pin): void { echo "[Blocked] Карта заблокирована\n"; }
public function selectAmount(float $amount): void { echo "[Blocked] Карта заблокирована\n"; }
public function ejectCard(): void
{ /* TODO: вернуть карту, → IdleState */ }
}
class ATM
{
private ATMState $state;
public ?string $cardNumber = null;
public int $pinAttempts = 0;
public float $requestedAmount = 0.0;
private array $cards = ["1234-5678" => "1234", "8765-4321" => "0000"];
private array $accounts = ["1234-5678" => 5000.0, "8765-4321" => 300.0];
public function __construct(private float $balance = 10000.0)
{
$this->state = new IdleState($this);
}
public function changeState(ATMState $state): void { $this->state = $state; }
public function verifyPin(string $pin): bool { return ($this->cards[$this->cardNumber] ?? null) === $pin; }
public function getAccountBalance(): float { return $this->accounts[$this->cardNumber] ?? 0.0; }
public function deduct(float $amount): void { $this->accounts[$this->cardNumber] -= $amount; $this->balance -= $amount; }
public function insertCard(string $cardNumber): void { $this->state->insertCard($cardNumber); }
public function enterPin(string $pin): void { $this->state->enterPin($pin); }
public function selectAmount(float $amount): void { $this->state->selectAmount($amount); }
public function ejectCard(): void { $this->state->ejectCard(); }
}
// Ожидаемый результат:
// $atm = new ATM();
// $atm->insertCard("1234-5678"); // Карта принята
// $atm->enterPin("9999"); // Неверный PIN (попытка 1/3)
// $atm->enterPin("9999"); // Неверный PIN (попытка 2/3)
// $atm->enterPin("1234"); // Успешная аутентификация
// $atm->selectAmount(500.0); // Запрос 500.0
// $atm->ejectCard(); // Выдано 500.0, карта возвращена
Задача 4. TCP-соединение
Реализуйте модель TCP-соединения с состояниями: Closed, Listen, SynReceived, Established. Переходы: Closed → Listen (open), Listen → SynReceived (syn), SynReceived → Established (ack), Established → Closed (close). В состоянии Established доступна отправка данных.
from __future__ import annotations
from abc import ABC, abstractmethod
class TCPState(ABC):
def __init__(self, connection: "TCPConnection") -> None:
self._conn = connection
@property
@abstractmethod
def name(self) -> str: ...
def open(self) -> None:
print(f"[{self.name}] Недопустимое действие")
def syn(self) -> None:
print(f"[{self.name}] Недопустимое действие")
def ack(self) -> None:
print(f"[{self.name}] Недопустимое действие")
def close(self) -> None:
print(f"[{self.name}] Недопустимое действие")
def send(self, data: str) -> None:
print(f"[{self.name}] Соединение не установлено")
class ClosedState(TCPState):
@property
def name(self) -> str:
return "CLOSED"
def open(self) -> None:
# TODO: → ListenState
pass
class ListenState(TCPState):
@property
def name(self) -> str:
return "LISTEN"
def syn(self) -> None:
# TODO: → SynReceivedState
pass
def close(self) -> None:
# TODO: → ClosedState
pass
class SynReceivedState(TCPState):
@property
def name(self) -> str:
return "SYN_RECEIVED"
def ack(self) -> None:
# TODO: → EstablishedState
pass
def close(self) -> None:
# TODO: → ClosedState
pass
class EstablishedState(TCPState):
@property
def name(self) -> str:
return "ESTABLISHED"
def send(self, data: str) -> None:
print(f"[ESTABLISHED] Отправка данных: '{data}'")
self._conn.log_sent(data)
def close(self) -> None:
# TODO: → ClosedState, выполнить four-way handshake (FIN)
pass
class TCPConnection:
def __init__(self) -> None:
self._state: TCPState = ClosedState(self)
self._sent: list[str] = []
print(f"TCP Connection создано. Состояние: {self._state.name}")
def change_state(self, state: TCPState) -> None:
self._state = state
print(f"TCP → {state.name}")
def log_sent(self, data: str) -> None:
self._sent.append(data)
def open(self) -> None: self._state.open()
def syn(self) -> None: self._state.syn()
def ack(self) -> None: self._state.ack()
def close(self) -> None: self._state.close()
def send(self, data: str) -> None: self._state.send(data)
@property
def state_name(self) -> str:
return self._state.name
# Ожидаемый результат:
# conn = TCPConnection() # CLOSED
# conn.send("hello") # [CLOSED] Соединение не установлено
# conn.open() # TCP → LISTEN
# conn.syn() # TCP → SYN_RECEIVED
# conn.ack() # TCP → ESTABLISHED
# conn.send("Hello!") # [ESTABLISHED] Отправка данных: 'Hello!'
# conn.send("World!") # [ESTABLISHED] Отправка данных: 'World!'
# conn.close() # TCP → CLOSED
# conn.send("late") # [CLOSED] Соединение не установлено
<?php
abstract class TCPState
{
public function __construct(protected TCPConnection $conn) {}
abstract public function getName(): string;
public function open(): void { echo "[{$this->getName()}] Недопустимое действие\n"; }
public function syn(): void { echo "[{$this->getName()}] Недопустимое действие\n"; }
public function ack(): void { echo "[{$this->getName()}] Недопустимое действие\n"; }
public function close(): void { echo "[{$this->getName()}] Недопустимое действие\n"; }
public function send(string $data): void { echo "[{$this->getName()}] Соединение не установлено\n"; }
}
class ClosedState extends TCPState
{
public function getName(): string { return "CLOSED"; }
public function open(): void { /* TODO: → ListenState */ }
}
class ListenState extends TCPState
{
public function getName(): string { return "LISTEN"; }
public function syn(): void { /* TODO: → SynReceivedState */ }
public function close(): void { /* TODO: → ClosedState */ }
}
class SynReceivedState extends TCPState
{
public function getName(): string { return "SYN_RECEIVED"; }
public function ack(): void { /* TODO: → EstablishedState */ }
public function close(): void { /* TODO: → ClosedState */ }
}
class EstablishedState extends TCPState
{
public function getName(): string { return "ESTABLISHED"; }
public function send(string $data): void
{
echo "[ESTABLISHED] Отправка данных: '$data'\n";
$this->conn->logSent($data);
}
public function close(): void { /* TODO: → ClosedState */ }
}
class TCPConnection
{
private TCPState $state;
private array $sent = [];
public function __construct()
{
$this->state = new ClosedState($this);
echo "TCP Connection создано. Состояние: {$this->state->getName()}\n";
}
public function changeState(TCPState $state): void
{
$this->state = $state;
echo "TCP → {$state->getName()}\n";
}
public function logSent(string $data): void { $this->sent[] = $data; }
public function open(): void { $this->state->open(); }
public function syn(): void { $this->state->syn(); }
public function ack(): void { $this->state->ack(); }
public function close(): void { $this->state->close(); }
public function send(string $data): void { $this->state->send($data); }
public function getStateName(): string { return $this->state->getName(); }
}
// Ожидаемый результат:
// $conn = new TCPConnection(); // CLOSED
// $conn->send("hello"); // [CLOSED] Соединение не установлено
// $conn->open(); // TCP → LISTEN
// $conn->syn(); // TCP → SYN_RECEIVED
// $conn->ack(); // TCP → ESTABLISHED
// $conn->send("Hello!"); // [ESTABLISHED] Отправка данных: 'Hello!'
// $conn->close(); // TCP → CLOSED
Задача 5. Торговый автомат (Vending Machine)
Реализуйте торговый автомат с состояниями: Idle (ожидание монеты), HasMoney (монета внесена, выбор товара), Dispensing (выдача товара), OutOfStock (нет товаров). Поддержите: внесение нескольких монет, сдача при недостаточной сумме, возврат денег.
from __future__ import annotations
from abc import ABC, abstractmethod
class VendingState(ABC):
def __init__(self, machine: "VendingMachine") -> None:
self._machine = machine
@abstractmethod
def insert_coin(self, amount: float) -> None: ...
@abstractmethod
def select_product(self, product: str) -> None: ...
@abstractmethod
def cancel(self) -> None: ...
@abstractmethod
def dispense(self) -> None: ...
class IdleState(VendingState):
def insert_coin(self, amount: float) -> None:
# TODO: добавить деньги в machine.balance, → HasMoneyState
pass
def select_product(self, product: str) -> None:
print("[Idle] Сначала внесите монету")
def cancel(self) -> None:
print("[Idle] Нечего отменять")
def dispense(self) -> None:
print("[Idle] Сначала выберите товар")
class HasMoneyState(VendingState):
def insert_coin(self, amount: float) -> None:
# TODO: добавить к текущему балансу
pass
def select_product(self, product: str) -> None:
# TODO: проверить наличие товара и достаточность суммы,
# установить machine.selected_product, → DispensingState
# если недостаточно средств — сообщить и остаться
pass
def cancel(self) -> None:
# TODO: вернуть деньги, → IdleState
pass
def dispense(self) -> None:
print("[HasMoney] Сначала выберите товар")
class DispensingState(VendingState):
def insert_coin(self, amount: float) -> None:
print("[Dispensing] Подождите, идёт выдача")
def select_product(self, product: str) -> None:
print("[Dispensing] Подождите, идёт выдача")
def cancel(self) -> None:
print("[Dispensing] Невозможно отменить")
def dispense(self) -> None:
# TODO: выдать товар, вернуть сдачу,
# если товаров больше нет → OutOfStockState, иначе → IdleState
pass
class OutOfStockState(VendingState):
def insert_coin(self, amount: float) -> None:
print("[OutOfStock] Нет товаров, монета возвращена")
def select_product(self, product: str) -> None:
print("[OutOfStock] Нет товаров")
def cancel(self) -> None:
print("[OutOfStock] Нет активной транзакции")
def dispense(self) -> None:
print("[OutOfStock] Нет товаров")
class VendingMachine:
def __init__(self) -> None:
self.balance: float = 0.0
self.selected_product: str | None = None
self._inventory: dict[str, tuple[float, int]] = {
# product: (price, quantity)
"Cola": (1.5, 3),
"Water": (1.0, 2),
"Chips": (2.0, 1),
}
self._state: VendingState = IdleState(self)
def change_state(self, state: VendingState) -> None:
self._state = state
def get_price(self, product: str) -> float | None:
item = self._inventory.get(product)
return item[0] if item else None
def get_stock(self, product: str) -> int:
item = self._inventory.get(product)
return item[1] if item else 0
def reduce_stock(self, product: str) -> None:
price, qty = self._inventory[product]
self._inventory[product] = (price, qty - 1)
def has_any_stock(self) -> bool:
return any(qty > 0 for _, qty in self._inventory.values())
def insert_coin(self, amount: float) -> None: self._state.insert_coin(amount)
def select_product(self, product: str) -> None: self._state.select_product(product)
def cancel(self) -> None: self._state.cancel()
def dispense(self) -> None: self._state.dispense()
# Ожидаемый результат:
# vm = VendingMachine()
# vm.select_product("Cola") # [Idle] Сначала внесите монету
# vm.insert_coin(1.0) # Баланс: 1.0
# vm.insert_coin(1.0) # Баланс: 2.0
# vm.select_product("Cola") # Выбрана Cola (1.5). Сдача: 0.5
# vm.dispense() # Выдаётся Cola. Сдача: 0.5 возвращена
# vm.insert_coin(1.0)
# vm.select_product("Water")
# vm.cancel() # Возврат 1.0
<?php
abstract class VendingState
{
public function __construct(protected VendingMachine $machine) {}
abstract public function insertCoin(float $amount): void;
abstract public function selectProduct(string $product): void;
abstract public function cancel(): void;
abstract public function dispense(): void;
}
class IdleState extends VendingState
{
public function insertCoin(float $amount): void
{ /* TODO: добавить к балансу, → HasMoneyState */ }
public function selectProduct(string $product): void
{ echo "[Idle] Сначала внесите монету\n"; }
public function cancel(): void { echo "[Idle] Нечего отменять\n"; }
public function dispense(): void { echo "[Idle] Сначала выберите товар\n"; }
}
class HasMoneyState extends VendingState
{
public function insertCoin(float $amount): void
{ /* TODO: добавить к балансу */ }
public function selectProduct(string $product): void
{ /* TODO: проверить наличие и баланс, → DispensingState */ }
public function cancel(): void
{ /* TODO: вернуть деньги, → IdleState */ }
public function dispense(): void { echo "[HasMoney] Сначала выберите товар\n"; }
}
class DispensingState extends VendingState
{
public function insertCoin(float $amount): void { echo "[Dispensing] Подождите\n"; }
public function selectProduct(string $product): void { echo "[Dispensing] Подождите\n"; }
public function cancel(): void { echo "[Dispensing] Невозможно отменить\n"; }
public function dispense(): void
{ /* TODO: выдать товар, сдача, → IdleState или OutOfStockState */ }
}
class OutOfStockState extends VendingState
{
public function insertCoin(float $amount): void { echo "[OutOfStock] Монета возвращена\n"; }
public function selectProduct(string $product): void { echo "[OutOfStock] Нет товаров\n"; }
public function cancel(): void { echo "[OutOfStock] Нет транзакции\n"; }
public function dispense(): void { echo "[OutOfStock] Нет товаров\n"; }
}
class VendingMachine
{
public float $balance = 0.0;
public ?string $selectedProduct = null;
private array $inventory = [
"Cola" => ["price" => 1.5, "qty" => 3],
"Water" => ["price" => 1.0, "qty" => 2],
"Chips" => ["price" => 2.0, "qty" => 1],
];
private VendingState $state;
public function __construct()
{
$this->state = new IdleState($this);
}
public function changeState(VendingState $state): void { $this->state = $state; }
public function getPrice(string $product): ?float { return $this->inventory[$product]["price"] ?? null; }
public function getStock(string $product): int { return $this->inventory[$product]["qty"] ?? 0; }
public function reduceStock(string $product): void { $this->inventory[$product]["qty"]--; }
public function hasAnyStock(): bool { return (bool) array_filter($this->inventory, fn($i) => $i["qty"] > 0); }
public function insertCoin(float $amount): void { $this->state->insertCoin($amount); }
public function selectProduct(string $product): void { $this->state->selectProduct($product); }
public function cancel(): void { $this->state->cancel(); }
public function dispense(): void { $this->state->dispense(); }
}
// Ожидаемый результат:
// $vm = new VendingMachine();
// $vm->selectProduct("Cola"); // [Idle] Сначала внесите монету
// $vm->insertCoin(1.0); // Баланс: 1.0
// $vm->insertCoin(1.0); // Баланс: 2.0
// $vm->selectProduct("Cola"); // Выбрана Cola (1.5). К оплате: 1.5
// $vm->dispense(); // Выдаётся Cola. Сдача: 0.5 возвращена
// $vm->insertCoin(1.0);
// $vm->cancel(); // Возврат 1.0