Посредник
Также известен как: Intermediary, Controller, Mediator
Посредник — это поведенческий паттерн проектирования, который позволяет уменьшить связанность множества классов между собой, благодаря перемещению этих связей в один класс-посредник.

Проблема
Предположим, что у вас есть диалог создания профиля пользователя. Он состоит из всевозможных элементов управления — текстовых полей, чекбоксов, кнопок.

Беспорядочные связи между элементами пользовательского интерфейса.
Отдельные элементы диалога должны взаимодействовать друг с другом. Так, например, чекбокс «у меня есть собака» открывает скрытое поле для ввода имени домашнего любимца, а клик по кнопке отправки запускает проверку значений всех полей формы.

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

Элементы интерфейса общаются через посредника.
Основные изменения произойдут внутри отдельных элементов диалога. Если раньше при получении клика от пользователя объект кнопки сам проверял значения полей диалога, то теперь его единственной обязанностью будет сообщить диалогу о том, что произошёл клик. Получив извещение, диалог выполнит все необходимые проверки полей. Таким образом, вместо нескольких зависимостей от остальных элементов кнопка получит только одну — от самого диалога.
Чтобы сделать код ещё более гибким, можно выделить общий интерфейс для всех посредников, то есть диалогов программы. Наша кнопка станет зависимой не от конкретного диалога создания пользователя, а от абстрактного, что позволит использовать её и в других диалогах.
Таким образом, посредник скрывает в себе все сложные связи и зависимости между классами отдельных компонентов программы. А чем меньше связей имеют классы, тем проще их изменять, расширять и повторно использовать.
Аналогия из жизни

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

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

Пример структурирования классов UI-диалогов.
По реакции на действия пользователей элементы не взаимодействуют напрямую, а всего лишь уведомляют посредника о том, что они изменились.
Посредник в виде диалога авторизации знает, как конкретные элементы должны взаимодействовать. Поэтому при получении уведомлений он может перенаправить вызов тому или иному элементу.
Применимость
Когда вам сложно менять некоторые классы из-за того, что они имеют множество хаотичных связей с другими классами.
Посредник позволяет поместить все эти связи в один класс, после чего вам будет легче их отрефакторить, сделать более понятными и гибкими.
Когда вы не можете повторно использовать класс, поскольку он зависит от уймы других классов.
После применения паттерна компоненты теряют прежние связи с другими компонентами, а всё их общение происходит косвенно, через объект-посредник.
Когда вам приходится создавать множество подклассов компонентов, чтобы использовать одни и те же компоненты в разных контекстах.
Если раньше изменение отношений в одном компоненте могли повлечь за собой лавину изменений во всех остальных компонентах, то теперь вам достаточно создать подкласс посредника и поменять в нём связи между компонентами.
Шаги реализации
-
Найдите группу тесно переплетённых классов, отвязав которые друг от друга, можно получить некоторую пользу. Например, чтобы повторно использовать их код в другой программе.
-
Создайте общий интерфейс посредников и опишите в нём методы для взаимодействия с компонентами. В простейшем случае достаточно одного метода для получения оповещений от компонентов.
Этот интерфейс необходим, если вы хотите повторно использовать классы компонентов для других задач. В этом случае всё, что нужно сделать — это создать новый класс конкретного посредника. - Реализуйте этот интерфейс в классе конкретного посредника. Поместите в него поля, которые будут содержать ссылки на все объекты компонентов.
- Вы можете пойти дальше и переместить код создания компонентов в класс посредника, после чего он может напоминать фабрику или фасад.
- Компоненты тоже должны иметь ссылку на объект посредника. Связь между ними удобнее всего установить, подавая посредника в параметры конструктора компонентов.
- Измените код компонентов так, чтобы они вызывали метод оповещения посредника, вместо методов других компонентов. С противоположной стороны, посредник должен вызывать методы нужного компонента, когда получает оповещение от компонента.
Преимущества и недостатки
Преимущества
- Устраняет зависимости между компонентами, позволяя повторно их использовать.
- Упрощает взаимодействие между компонентами.
- Централизует управление в одном месте.
Недостатки
- Посредник может сильно раздуться.
Примеры реализации паттерна
Python
- Сложность: 2/3
- Популярность: 0/3
- Применимость: Пожалуй, самое популярное применение Посредника в Python-коде — это связь нескольких компонентов GUI одной программы.
Этот пример показывает структуру паттерна Посредник, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
PHP
- Сложность: 2/3
- Популярность: 0/3
- Применимость: Посредник не столь актуален в PHP, как в других языках, из-за того, что разные компоненты приложения не часто общаются друг с другом в пределах одной сессии скрипта.
Тем не менее, примерами паттерна могут служитьEventDispatcher-ы многих фреймворков, а также некоторые реализации контроллеров в MVC фреймворках.
Этот пример показывает структуру паттерна Посредник, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире 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 111 112 113 114 115 | |
В этом примере паттерн Посредник расширяет базовую идею паттерна Наблюдатель, предоставляя централизованный диспетчер событий. Он позволяет любому объекту отслеживать и запускать события в других объектах, независимо от их классов.
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | |
UserRepository: Loading user records from a file.
EventDispatcher: Broadcasting the 'users:init' event.
Logger: I've written 'users:init' entry to the log.
UserRepository: Creating a user.
EventDispatcher: Broadcasting the 'users:created' event.
OnboardingNotification: The notification has been emailed!
Logger: I've written 'users:created' entry to the log.
User: I can now delete myself without worrying about the repository.
EventDispatcher: Broadcasting the 'users:deleted' event.
UserRepository: Deleting a user.
Logger: I've written 'users:deleted' entry to the log.
Задачи
Задача 1. Чат-комната
Реализуйте систему чат-комнаты, где пользователи общаются через посредника, а не напрямую. Посредник управляет историей сообщений и поддерживает приватные сообщения между участниками.
from __future__ import annotations
from abc import ABC, abstractmethod
from datetime import datetime
class ChatMediator(ABC):
@abstractmethod
def send_message(self, sender: "User", message: str,
recipient: "User" = None) -> None: ...
@abstractmethod
def add_user(self, user: "User") -> None: ...
class ChatRoom(ChatMediator):
def __init__(self, name: str) -> None:
self.name = name
# TODO: хранить список пользователей и историю сообщений
def add_user(self, user: "User") -> None:
# TODO: добавить пользователя и оповестить комнату
pass
def send_message(self, sender: "User", message: str,
recipient: "User" = None) -> None:
# TODO: если recipient указан — приватное сообщение,
# иначе — broadcast всем кроме отправителя
pass
def get_history(self) -> list[str]:
# TODO: вернуть историю сообщений
pass
class User:
def __init__(self, name: str) -> None:
self.name = name
self._mediator: ChatMediator | None = None
def join(self, room: ChatMediator) -> None:
# TODO: подключиться к комнате
pass
def send(self, message: str, to: "User" = None) -> None:
# TODO: отправить сообщение через посредника
pass
def receive(self, message: str) -> None:
print(f"[{self.name}] {message}")
# Ожидаемый результат:
# room = ChatRoom("General")
# alice, bob, carol = User("Alice"), User("Bob"), User("Carol")
# alice.join(room); bob.join(room); carol.join(room)
# alice.send("Всем привет!")
# bob.send("Привет, Алиса!", to=alice)
# carol.send("Как дела?")
# print(room.get_history())
<?php
interface ChatMediator
{
public function sendMessage(User $sender, string $message, ?User $recipient = null): void;
public function addUser(User $user): void;
}
class ChatRoom implements ChatMediator
{
private string $name;
// TODO: хранить список пользователей и историю сообщений
public function __construct(string $name)
{
$this->name = $name;
}
public function addUser(User $user): void
{
// TODO: добавить пользователя и оповестить комнату
}
public function sendMessage(User $sender, string $message, ?User $recipient = null): void
{
// TODO: если $recipient задан — приватное сообщение,
// иначе — broadcast всем кроме отправителя
}
public function getHistory(): array
{
// TODO: вернуть историю сообщений
}
}
class User
{
private string $name;
private ?ChatMediator $mediator = null;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string { return $this->name; }
public function join(ChatMediator $room): void
{
// TODO: подключиться к комнате
}
public function send(string $message, ?User $to = null): void
{
// TODO: отправить через посредника
}
public function receive(string $message): void
{
echo "[{$this->name}] $message\n";
}
}
// Ожидаемый результат:
// $room = new ChatRoom("General");
// $alice = new User("Alice"); $bob = new User("Bob"); $carol = new User("Carol");
// $alice->join($room); $bob->join($room); $carol->join($room);
// $alice->send("Всем привет!");
// $bob->send("Привет, Алиса!", $alice);
// $carol->send("Как дела?");
// print_r($room->getHistory());
Задача 2. Система управления воздушным движением
Реализуйте систему управления полётами через диспетчерскую башню (посредник). Самолёты не общаются напрямую — только через башню. Башня следит за взлётно-посадочными полосами и координирует посадку и взлёт.
from __future__ import annotations
from abc import ABC, abstractmethod
from enum import Enum
class RunwayStatus(Enum):
FREE = "free"
OCCUPIED = "occupied"
class AirTrafficMediator(ABC):
@abstractmethod
def request_landing(self, aircraft: "Aircraft") -> bool: ...
@abstractmethod
def request_takeoff(self, aircraft: "Aircraft") -> bool: ...
@abstractmethod
def notify_landed(self, aircraft: "Aircraft") -> None: ...
@abstractmethod
def notify_taken_off(self, aircraft: "Aircraft") -> None: ...
class ControlTower(AirTrafficMediator):
def __init__(self, runways: int = 2) -> None:
# TODO: хранить состояние полос и очередь ожидания
pass
def request_landing(self, aircraft: "Aircraft") -> bool:
# TODO: если есть свободная полоса — разрешить посадку,
# иначе добавить в очередь ожидания
pass
def request_takeoff(self, aircraft: "Aircraft") -> bool:
# TODO: аналогично для взлёта
pass
def notify_landed(self, aircraft: "Aircraft") -> None:
# TODO: освободить полосу и разрешить следующему из очереди
pass
def notify_taken_off(self, aircraft: "Aircraft") -> None:
# TODO: освободить полосу
pass
class Aircraft:
def __init__(self, flight_id: str, tower: AirTrafficMediator) -> None:
self.flight_id = flight_id
self._tower = tower
def land(self) -> None:
# TODO: запросить посадку через башню
pass
def take_off(self) -> None:
# TODO: запросить взлёт через башню
pass
# Ожидаемый результат:
# tower = ControlTower(runways=1)
# f1 = Aircraft("SU-101", tower)
# f2 = Aircraft("SU-202", tower)
# f3 = Aircraft("SU-303", tower)
# f1.land() # SU-101: Посадка разрешена, полоса 1
# f2.land() # SU-202: Ожидание...
# f3.land() # SU-303: Ожидание...
# f1.take_off() # башня освобождает полосу, SU-202 получает разрешение
<?php
enum RunwayStatus { case Free; case Occupied; }
interface AirTrafficMediator
{
public function requestLanding(Aircraft $aircraft): bool;
public function requestTakeoff(Aircraft $aircraft): bool;
public function notifyLanded(Aircraft $aircraft): void;
public function notifyTakenOff(Aircraft $aircraft): void;
}
class ControlTower implements AirTrafficMediator
{
// TODO: хранить состояние полос и очередь ожидания
public function __construct(int $runways = 2)
{
// TODO: инициализировать полосы
}
public function requestLanding(Aircraft $aircraft): bool
{
// TODO: выдать разрешение или добавить в очередь
}
public function requestTakeoff(Aircraft $aircraft): bool
{
// TODO: аналогично для взлёта
}
public function notifyLanded(Aircraft $aircraft): void
{
// TODO: освободить полосу и разрешить следующему из очереди
}
public function notifyTakenOff(Aircraft $aircraft): void
{
// TODO: освободить полосу
}
}
class Aircraft
{
public function __construct(
private readonly string $flightId,
private readonly AirTrafficMediator $tower,
) {}
public function getFlightId(): string { return $this->flightId; }
public function land(): void
{
// TODO: запросить посадку через башню
}
public function takeOff(): void
{
// TODO: запросить взлёт через башню
}
}
// Ожидаемый результат:
// $tower = new ControlTower(runways: 1);
// $f1 = new Aircraft("SU-101", $tower);
// $f2 = new Aircraft("SU-202", $tower);
// $f3 = new Aircraft("SU-303", $tower);
// $f1->land(); // SU-101: Посадка разрешена, полоса 1
// $f2->land(); // SU-202: Ожидание...
// $f3->land(); // SU-303: Ожидание...
// $f1->takeOff(); // башня освобождает полосу, SU-202 получает разрешение
Задача 3. Форма регистрации с зависимыми полями
Реализуйте посредника для UI-формы регистрации, где поля взаимодействуют через него. Компоненты: чекбокс «Юридическое лицо» (показывает/скрывает поле «ИНН»), поле «Страна» (меняет формат телефона), кнопка «Отправить» (активируется только когда все обязательные поля заполнены).
from __future__ import annotations
from abc import ABC, abstractmethod
class FormMediator(ABC):
@abstractmethod
def notify(self, sender: "FormComponent", event: str) -> None: ...
class FormComponent(ABC):
def __init__(self, name: str) -> None:
self.name = name
self._mediator: FormMediator | None = None
self.enabled = True
self.visible = True
def set_mediator(self, mediator: FormMediator) -> None:
self._mediator = mediator
def notify(self, event: str) -> None:
if self._mediator:
self._mediator.notify(self, event)
class Checkbox(FormComponent):
def __init__(self, name: str) -> None:
super().__init__(name)
self.checked = False
def toggle(self) -> None:
self.checked = not self.checked
self.notify("change")
class TextField(FormComponent):
def __init__(self, name: str, placeholder: str = "") -> None:
super().__init__(name)
self.value = ""
self.placeholder = placeholder
def set_value(self, value: str) -> None:
self.value = value
self.notify("input")
class SelectField(FormComponent):
def __init__(self, name: str, options: list[str]) -> None:
super().__init__(name)
self.options = options
self.selected: str | None = None
def select(self, option: str) -> None:
self.selected = option
self.notify("change")
class Button(FormComponent):
def click(self) -> None:
if self.enabled:
self.notify("click")
else:
print(f"Button '{self.name}' is disabled")
class RegistrationForm(FormMediator):
def __init__(self) -> None:
# TODO: создать компоненты: is_legal (Checkbox), inn (TextField),
# country (SelectField), phone (TextField),
# email (TextField), submit (Button)
# TODO: зарегистрировать self как медиатор для каждого
pass
def notify(self, sender: FormComponent, event: str) -> None:
# TODO:
# - is_legal.change → показать/скрыть inn
# - country.change → обновить placeholder у phone
# - любой TextField.input → проверить активность submit
pass
def _update_submit_state(self) -> None:
# TODO: submit активен если email и phone заполнены,
# и если is_legal — то inn тоже заполнен
pass
# Ожидаемый результат:
# form = RegistrationForm()
# print(form.inn.visible) # False (скрыто по умолчанию)
# form.is_legal.toggle()
# print(form.inn.visible) # True
# form.country.select("Russia")
# print(form.phone.placeholder) # "+7 (___) ___-__-__"
# form.email.set_value("a@b.com")
# form.phone.set_value("+71234567890")
# form.inn.set_value("1234567890")
# print(form.submit.enabled) # True
# form.submit.click() # Отправка формы...
<?php
interface FormMediator
{
public function notify(FormComponent $sender, string $event): void;
}
abstract class FormComponent
{
protected ?FormMediator $mediator = null;
public bool $enabled = true;
public bool $visible = true;
public function __construct(protected readonly string $name) {}
public function getName(): string { return $this->name; }
public function setMediator(FormMediator $mediator): void
{
$this->mediator = $mediator;
}
protected function notify(string $event): void
{
$this->mediator?->notify($this, $event);
}
}
class Checkbox extends FormComponent
{
public bool $checked = false;
public function toggle(): void
{
$this->checked = !$this->checked;
$this->notify("change");
}
}
class TextField extends FormComponent
{
public string $value = "";
public string $placeholder = "";
public function setValue(string $value): void
{
$this->value = $value;
$this->notify("input");
}
}
class SelectField extends FormComponent
{
public ?string $selected = null;
public function __construct(string $name, private readonly array $options)
{
parent::__construct($name);
}
public function select(string $option): void
{
$this->selected = $option;
$this->notify("change");
}
}
class Button extends FormComponent
{
public function click(): void
{
if ($this->enabled) {
$this->notify("click");
} else {
echo "Button '{$this->name}' is disabled\n";
}
}
}
class RegistrationForm implements FormMediator
{
public Checkbox $isLegal;
public TextField $inn;
public SelectField $country;
public TextField $phone;
public TextField $email;
public Button $submit;
public function __construct()
{
// TODO: создать компоненты и связать с посредником
}
public function notify(FormComponent $sender, string $event): void
{
// TODO:
// - isLegal.change → показать/скрыть inn
// - country.change → обновить placeholder у phone
// - TextField.input → проверить активность submit
}
private function updateSubmitState(): void
{
// TODO: submit активен если email и phone заполнены,
// и если isLegal — то inn тоже заполнен
}
}
// Ожидаемый результат:
// $form = new RegistrationForm();
// var_dump($form->inn->visible); // false
// $form->isLegal->toggle();
// var_dump($form->inn->visible); // true
// $form->country->select("Russia");
// echo $form->phone->placeholder; // "+7 (___) ___-__-__"
// $form->email->setValue("a@b.com");
// $form->phone->setValue("+71234567890");
// $form->inn->setValue("1234567890");
// var_dump($form->submit->enabled); // true
// $form->submit->click(); // Отправка формы...
Задача 4. Торговая площадка (биржа)
Реализуйте упрощённую биржу как посредника между покупателями и продавцами. Трейдеры не знают друг о друге — они только подают заявки через биржу. Биржа сводит встречные заявки (ордера) по принципу «лучшая цена».
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
class OrderType(Enum):
BUY = "buy"
SELL = "sell"
@dataclass
class Order:
trader: "Trader"
order_type: OrderType
asset: str
quantity: int
price: float
order_id: int = field(default=0)
class Exchange:
def __init__(self) -> None:
# TODO: хранить книгу ордеров (buy_orders, sell_orders)
self._next_id = 1
def place_order(self, order: Order) -> None:
# TODO: назначить order_id, попробовать исполнить заявку,
# если нет встречной — добавить в книгу ордеров
pass
def _match_orders(self, order: Order) -> None:
# TODO: для BUY ищем SELL с price <= order.price (наименьшую),
# для SELL ищем BUY с price >= order.price (наибольшую).
# При совпадении оповестить обоих трейдеров через notify_trade()
pass
def cancel_order(self, order_id: int, trader: "Trader") -> bool:
# TODO: снять заявку из книги ордеров
pass
def get_order_book(self, asset: str) -> dict:
# TODO: вернуть {"buy": [...], "sell": [...]}
pass
class Trader:
def __init__(self, name: str, exchange: Exchange) -> None:
self.name = name
self._exchange = exchange
self.trades: list[str] = []
def buy(self, asset: str, quantity: int, price: float) -> Order:
# TODO: создать и разместить ордер на покупку
pass
def sell(self, asset: str, quantity: int, price: float) -> Order:
# TODO: создать и разместить ордер на продажу
pass
def notify_trade(self, message: str) -> None:
self.trades.append(message)
print(f"[{self.name}] {message}")
# Ожидаемый результат:
# exchange = Exchange()
# alice = Trader("Alice", exchange)
# bob = Trader("Bob", exchange)
# alice.sell("AAPL", quantity=10, price=150.0)
# bob.buy("AAPL", quantity=10, price=155.0)
# # [Alice] Сделка: продано 10 AAPL по 150.0
# # [Bob] Сделка: куплено 10 AAPL по 150.0
# print(exchange.get_order_book("AAPL")) # {"buy": [], "sell": []}
<?php
enum OrderType: string { case Buy = "buy"; case Sell = "sell"; }
class Order
{
public int $orderId = 0;
public function __construct(
public readonly Trader $trader,
public readonly OrderType $type,
public readonly string $asset,
public readonly int $quantity,
public readonly float $price,
) {}
}
class Exchange
{
private int $nextId = 1;
// TODO: хранить книгу ордеров
public function placeOrder(Order $order): void
{
// TODO: назначить orderId, попробовать исполнить,
// если нет встречной — добавить в книгу
}
private function matchOrders(Order $order): void
{
// TODO: найти встречный ордер по лучшей цене,
// оповестить трейдеров через notifyTrade()
}
public function cancelOrder(int $orderId, Trader $trader): bool
{
// TODO: снять заявку
}
public function getOrderBook(string $asset): array
{
// TODO: вернуть ["buy" => [...], "sell" => [...]]
}
}
class Trader
{
public array $trades = [];
public function __construct(
private readonly string $name,
private readonly Exchange $exchange,
) {}
public function getName(): string { return $this->name; }
public function buy(string $asset, int $quantity, float $price): Order
{
// TODO: создать BUY-ордер и разместить на бирже
}
public function sell(string $asset, int $quantity, float $price): Order
{
// TODO: создать SELL-ордер и разместить на бирже
}
public function notifyTrade(string $message): void
{
$this->trades[] = $message;
echo "[{$this->name}] $message\n";
}
}
// Ожидаемый результат:
// $exchange = new Exchange();
// $alice = new Trader("Alice", $exchange);
// $bob = new Trader("Bob", $exchange);
// $alice->sell("AAPL", 10, 150.0);
// $bob->buy("AAPL", 10, 155.0);
// // [Alice] Сделка: продано 10 AAPL по 150.0
// // [Bob] Сделка: куплено 10 AAPL по 150.0
// print_r($exchange->getOrderBook("AAPL")); // ["buy" => [], "sell" => []]
Задача 5. Игровой движок: система событий
Реализуйте игровой посредник, который координирует взаимодействие игровых объектов (персонаж, враги, UI, звуковая система). Объекты не знают друг о друге — только генерируют события. Посредник маршрутизирует события: player:damage → обновить HP-бар UI и воспроизвести звук, enemy:killed → обновить счёт и проверить победу, level:complete → показать экран победы.
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any
class GameMediator(ABC):
@abstractmethod
def notify(self, sender: "GameObject", event: str,
data: Any = None) -> None: ...
class GameObject(ABC):
def __init__(self, name: str, mediator: GameMediator) -> None:
self.name = name
self._mediator = mediator
def emit(self, event: str, data: Any = None) -> None:
self._mediator.notify(self, event, data)
class Player(GameObject):
def __init__(self, mediator: GameMediator, hp: int = 100) -> None:
super().__init__("Player", mediator)
self.hp = hp
self.score = 0
def take_damage(self, amount: int) -> None:
self.hp = max(0, self.hp - amount)
self.emit("player:damage", {"hp": self.hp, "damage": amount})
if self.hp == 0:
self.emit("player:dead")
def add_score(self, points: int) -> None:
self.score += points
self.emit("player:score", {"score": self.score})
class Enemy(GameObject):
def __init__(self, name: str, mediator: GameMediator,
hp: int = 30) -> None:
super().__init__(name, mediator)
self.hp = hp
self.alive = True
def take_damage(self, amount: int) -> None:
self.hp = max(0, self.hp - amount)
if self.hp == 0 and self.alive:
self.alive = False
self.emit("enemy:killed", {"name": self.name})
class UISystem(GameObject):
def __init__(self, mediator: GameMediator) -> None:
super().__init__("UI", mediator)
self.hp_bar = 100
self.score = 0
def update_hp(self, hp: int) -> None:
self.hp_bar = hp
print(f"[UI] HP: {hp}")
def update_score(self, score: int) -> None:
self.score = score
print(f"[UI] Score: {score}")
def show_game_over(self) -> None:
print("[UI] GAME OVER")
def show_victory(self) -> None:
print("[UI] YOU WIN!")
class AudioSystem(GameObject):
def __init__(self, mediator: GameMediator) -> None:
super().__init__("Audio", mediator)
def play(self, sound: str) -> None:
print(f"[Audio] Playing: {sound}")
class GameLevel(GameMediator):
def __init__(self, enemies_to_kill: int = 3) -> None:
self.enemies_to_kill = enemies_to_kill
self.killed = 0
# TODO: создать player, ui, audio; список врагов создаётся снаружи
def notify(self, sender: GameObject, event: str,
data: Any = None) -> None:
# TODO:
# player:damage → ui.update_hp, audio.play("hit.wav")
# player:dead → audio.play("gameover.wav"), ui.show_game_over
# player:score → ui.update_score
# enemy:killed → player.add_score(100), killed++,
# проверить если killed == enemies_to_kill →
# emit level:complete
# level:complete → audio.play("victory.wav"), ui.show_victory
pass
# Ожидаемый результат:
# level = GameLevel(enemies_to_kill=2)
# e1 = Enemy("Goblin", level)
# e2 = Enemy("Orc", level)
# level.player.take_damage(20)
# # [UI] HP: 80
# # [Audio] Playing: hit.wav
# e1.take_damage(30)
# # [UI] Score: 100
# # [Audio] Playing: kill.wav
# e2.take_damage(30)
# # [UI] Score: 200
# # [Audio] Playing: kill.wav
# # [Audio] Playing: victory.wav
# # [UI] YOU WIN!
<?php
interface GameMediator
{
public function notify(GameObject $sender, string $event, mixed $data = null): void;
}
abstract class GameObject
{
public function __construct(
protected readonly string $name,
protected readonly GameMediator $mediator,
) {}
public function getName(): string { return $this->name; }
protected function emit(string $event, mixed $data = null): void
{
$this->mediator->notify($this, $event, $data);
}
}
class Player extends GameObject
{
public int $hp = 100;
public int $score = 0;
public function __construct(GameMediator $mediator, int $hp = 100)
{
parent::__construct("Player", $mediator);
$this->hp = $hp;
}
public function takeDamage(int $amount): void
{
$this->hp = max(0, $this->hp - $amount);
$this->emit("player:damage", ["hp" => $this->hp, "damage" => $amount]);
if ($this->hp === 0) {
$this->emit("player:dead");
}
}
public function addScore(int $points): void
{
$this->score += $points;
$this->emit("player:score", ["score" => $this->score]);
}
}
class Enemy extends GameObject
{
public int $hp;
public bool $alive = true;
public function __construct(string $name, GameMediator $mediator, int $hp = 30)
{
parent::__construct($name, $mediator);
$this->hp = $hp;
}
public function takeDamage(int $amount): void
{
$this->hp = max(0, $this->hp - $amount);
if ($this->hp === 0 && $this->alive) {
$this->alive = false;
$this->emit("enemy:killed", ["name" => $this->name]);
}
}
}
class UISystem extends GameObject
{
public int $hpBar = 100;
public int $score = 0;
public function __construct(GameMediator $mediator)
{
parent::__construct("UI", $mediator);
}
public function updateHp(int $hp): void { $this->hpBar = $hp; echo "[UI] HP: $hp\n"; }
public function updateScore(int $score): void { $this->score = $score; echo "[UI] Score: $score\n"; }
public function showGameOver(): void { echo "[UI] GAME OVER\n"; }
public function showVictory(): void { echo "[UI] YOU WIN!\n"; }
}
class AudioSystem extends GameObject
{
public function __construct(GameMediator $mediator)
{
parent::__construct("Audio", $mediator);
}
public function play(string $sound): void { echo "[Audio] Playing: $sound\n"; }
}
class GameLevel implements GameMediator
{
public Player $player;
public UISystem $ui;
public AudioSystem $audio;
private int $killed = 0;
public function __construct(private readonly int $enemiesToKill = 3)
{
// TODO: создать player, ui, audio с $this как посредником
}
public function notify(GameObject $sender, string $event, mixed $data = null): void
{
// TODO:
// player:damage → ui->updateHp, audio->play("hit.wav")
// player:dead → audio->play("gameover.wav"), ui->showGameOver
// player:score → ui->updateScore
// enemy:killed → player->addScore(100), killed++,
// если killed == enemiesToKill → emit level:complete
// level:complete → audio->play("victory.wav"), ui->showVictory
}
}
// Ожидаемый результат:
// $level = new GameLevel(enemiesToKill: 2);
// $e1 = new Enemy("Goblin", $level);
// $e2 = new Enemy("Orc", $level);
// $level->player->takeDamage(20);
// // [UI] HP: 80
// // [Audio] Playing: hit.wav
// $e1->takeDamage(30);
// // [UI] Score: 100
// // [Audio] Playing: kill.wav
// $e2->takeDamage(30);
// // [UI] Score: 200
// // [Audio] Playing: kill.wav
// // [Audio] Playing: victory.wav
// // [UI] YOU WIN!