Паттерн «Посредник» в Python

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

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

Ключевая идея

Основная идея паттерна Посредник —"Не общайтесь напрямую; координируйте через посредника."Он решает сложность взаимодействий «многие ко многим» между объектами, делая систему проще в поддержке и расширении.


Зачем нужен паттерн Посредник?

Сценарий проблемы

Предположим, мы разрабатываем систему чата с несколькими пользователями, которым нужно отправлять сообщения друг другу. Без посредника код может выглядеть так:

Пример

class User:
    def __init__(self, name):
        self.name = name
        self.other_users = []  # Нужно знать всех остальных пользователей
   
    def send_message(self, message, to_user):
        print(f"{self.name} отправил пользователю {to_user.name}: {message}")
        to_user.receive_message(message, self)
   
    def receive_message(self, message, from_user):
        print(f{self.name} получил сообщение от {from_user.name}: {message})

# Пример использования
user1 = User(Сяомин)
user2 = User(Сяохун)
user3 = User(Сяоган)

# Каждому пользователю нужно знать всех остальных пользователей
user1.other_users = [user2, user3]
user2.other_users = [user1, user3]
user3.other_users = [user1, user2]

user1.send_message(Привет!, user2)

Недостатки этого дизайна:

  • Высокая связанность между объектами; каждый пользователь должен знать всех остальных пользователей.
  • Добавление нового пользователя требует изменения всех существующих пользователей.
  • Сложно поддерживать; логика взаимодействия разбросана по объектам.

Решение

Паттерн «Посредник» решает эту проблему, вводя чат-комнату (посредника):

Пример

class ChatRoom:
    def __init__(self):
        self.users = {}
   
    def register_user(self, user):
        self.users[user.name] = user
        user.chat_room = self
   
    def send_message(self, message, from_user, to_user_name=Отсутствует):
        if to_user_name:  # Личный чат
            if to_user_name in self.users:
                self.users[to_user_name].receive_message(message, from_user)
        else:  # Групповой чат
            for user_name, user in self.users.items():
                if user_name != from_user.name:
                    user.receive_message(message, from_user)

class User:
    def __init__(self, name):
        self.name = name
        self.chat_room = None
   
    def send_message(self, message, to_user_name=None):
        if self.chat_room:
            self.chat_room.send_message(message, self, to_user_name)
   
    def receive_message(self, message, from_user):
        print(f{self.name} получил сообщение от {from_user.name}: {message})

# Пример использования
chat_room = ChatRoom()

user1 = User(Сяомин)
user2 = User(Сяохун)
user3 = User(Сяоган)

chat_room.register_user(user1)
chat_room.register_user(user2)
chat_room.register_user(user3)

user1.send_message(Привет всем!)  # Широковещательное сообщение
user2.send_message(Привет, Xiaoming!, Xiaoming)  # Личный чат

Реализация паттерна Посредник

Базовая структура

Давайте рассмотрим реализацию паттерна Посредник на более полном примере:

Пример

from abc import ABC, abstractmethod
from typing import List

# Абстрактный посредник
class Mediator(ABC):
    @abstractmethod
    def notify(self, sender: object, event: str, data: dict = None):
        pass

# Конкретный посредник - Чат-комната
class ChatRoomMediator(Mediator):
    def __init__(self):
        self.users: List[User] = []
   
    def add_user(self, user):
        self.users.append(user)
        user.set_mediator(self)
   
    def notify(self, sender, event, data=Отсутствует):
        if event == "send_message":
            message = data.get("message")
            target_user = data.get("target_user")
           
            if target_user:  # Приватный чат
                for user in self.users:
                    if user.name == target_user:
                        user.receive_message(message, sender.name)
            else:  # Групповой чат
                for user in self.users:
                    if user != sender:
                        user.receive_message(message, sender.name)
       
        elif event == "user_joined":
            message = fСистема: {sender.name} присоединился к чату
            for user in self.users:
                if user != sender:
                    user.receive_message(message, System)

# Базовый класс компонента
class BaseComponent:
    def __init__(self):
        self._mediator = None
   
    def set_mediator(self, mediator: Mediator):
        self._mediator = mediator

# Конкретный компонент - User
class User(BaseComponent):
    def __init__(self, name):
        super().__init__()
        self.name = name
   
    def send_message(self, message, target_user=None):
        print(f{self.name} отправил(а) сообщение: {message})
        self._mediator.notify(self, "send_message", {
            "message": message,
            "target_user": target_user
        })
   
    def join_chat(self):
        self._mediator.notify(self, "user_joined")
   
    def receive_message(self, message, from_name):
        print(f{self.name} получил(а) сообщение от {from_name}: {message})

# Пример использования
def main():
    # Создать посредника
    chat_room = ChatRoomMediator()
   
    # Создать пользователя
    alice = User("Алиса")
    bob = User("Боб")
    charlie = User("Чарли")
   
    # Зарегистрировать пользователей в чате
    chat_room.add_user(alice)
    chat_room.add_user(bob)
    chat_room.add_user(charlie)
   
    print(=== Демонстрация чата ===)
   
    # Алиса присоединяется к чату
    alice.join_chat()
   
    # Отправить сообщение
    alice.send_message(Привет всем, я Алиса!)
    bob.send_message(Добро пожаловать, Alice!)
    charlie.send_message(Привет, Alice!, "Alice")  # Личный чат
   
    # Bob отправляет групповое сообщение
    bob.send_message(Кто-нибудь онлайн?)

if __name__ == "__main__":
    main()

Вывод

=== 聊天室演示 ===
系统: Alice 加入了聊天室
Alice 发送消息: 大家好,我是 Alice!
Bob 收到来自 Alice 的消息: 大家好,我是 Alice!
Charlie 收到来自 Alice 的消息: 大家好,我是 Alice!
Bob 发送消息: 欢迎 Alice!
Alice 收到来自 Bob 的消息: 欢迎 Alice!
Charlie 收到来自 Bob 的消息: 欢迎 Alice!
Charlie 发送消息: Alice 你好!
Alice 收到来自 Charlie 的消息: Alice 你好!
Bob 发送消息: 有人在线吗?
Alice 收到来自 Bob 的消息: 有人在线吗?
Charlie 收到来自 Bob 的消息: 有人在线吗?

UML-структура паттерна «Посредник»

Описание структуры

  • Mediator (интерфейс посредника): определяет интерфейс взаимодействия
  • ConcreteMediator (конкретный посредник): реализует логику координации и знает все компоненты
  • BaseComponent (базовый компонент): содержит ссылку на посредника
  • ConcreteComponent (конкретный компонент): реализует конкретную бизнес-логику

Реальные сценарии применения

Сценарий 1: GUI-приложение

В графическом пользовательском интерфейсе различные элементы управления (кнопки, текстовые поля, флажки и т. д.) координируют взаимодействие через посредника:

Пример

class DialogMediator:
    def __init__(self):
        self.login_button = None
        self.username_input = None
        self.password_input = None
        self.remember_checkbox = None
   
    def notify(self, sender, event):
        if event == "username_changed" or event == "password_changed":
            # Когда введены имя пользователя или пароль, проверьте, можно ли включить кнопку входа
            username = self.username_input.get_text()
            password = self.password_input.get_text()
            self.login_button.set_enabled(bool(username and пароль))
       
        elif event == "login_clicked":
            # Логика обработки входа
            username = self.username_input.get_text()
            password = self.password_input.get_text()
            remember = self.remember_checkbox.is_checked()
            print(fЛогин: {username}, Запомнить меня: {remember})
       
        elif event == "remember_changed":
            print(Опция «Запомнить меня» изменена)

class UIComponent:
    def __init__(self, mediator=None):
        self.mediator = mediator
   
    def set_mediator(self, mediator):
        self.mediator = mediator

class Button(UI):
    def click(self):
        if self.mediator:
            self.mediator.notify(self, "login_clicked")
   
    def set_enabled(self, enabled):
        print(fButton {'Включить' if enabled else 'Отключить'})

class TextInput(UIComponent):
    def __init__(self, mediator=None):
        super().__init__(mediator)
        self._text = ""
   
    def set_text(self, text):
        self._text = text
        if self.mediator:
            self.mediator.notify(self, "username_changed")
   
    def get_text(self):
        return self._text

# Пример использования
dialog = DialogMediator()

login_btn = Button()
username_input = TextInput()
password_input = TextInput()

dialog.login_button = login_btn
dialog.username_input = username_input
dialog.password_input = password_input

login_btn.set_mediator(dialog)
username_input.set_mediator(dialog)
password_input.set_mediator(dialog)

# Имитировать ввод пользователя
username_input.set_text("user123")
password_input.set_text("pass123")
login_btn.click()

Сценарий 2: Система заказов электронной коммерции

В системе электронной коммерции обработка заказов включает несколько подсистем, таких как управление запасами, платежи и логистика:

Пример

class OrderMediator:
    def __init__(self):
        self.inventory_system = None
        self.payment_system = None
        self.shipping_system = None
        self.notification_system = None
   
    def place_order(self, order_data):
        print(=== Начало обработки заказа ===)
       
        # Проверить запасы
        if not self.inventory_system.check_stock(order_data):
            return Недостаточно запасов
       
        # Обработать платеж
        payment_result = self.payment_system.process_payment(order_data)
        if not payment_result:
            return Платёж не прошёл
       
        # Обновить инвентарь
        self.inventory_system.update_stock(order_data)
       
        # Организовать доставку
        shipping_info = self.shipping_system.schedule_delivery(order_data)
       
        # Отправить уведомление
        self.notification_system.send_confirmation(order_data, shipping_info)
       
        return Заказ успешно обработан

class InventorySystem:
    def check_stock(self, order_data):
        print(Проверка запасов...)
        return True
   
    def update_stock(self, order_data):
        print(Обновление запасов...)

class PaymentSystem:
    def process_payment(self, order_data):
        print(Обработка платежа...)
        return True

class ShippingSystem:
    def schedule_delivery(self, order_data):
        print(Организация отправки...)
        return Номер отслеживания: SF123456789

class NotificationSystem:
    def send_confirmation(self, order_data, shipping_info):
        print(fОтправка письма с подтверждением, информация о доставке: {shipping_info})

# Пример использования
mediator = OrderMediator()

mediator.inventory_system = InventorySystem()
mediator.payment_system = PaymentSystem()
mediator.shipping_system = ShippingSystem()
mediator.notification_system = Notification()

order_data = {"product_id": "123", "quantity": 2, "user_id": "user001"}
result = mediator.place_order(order_data)
print(fРезультат заказа: {result})

Преимущества и недостатки паттерна «Посредник»

Преимущества

Преимущества Описание
Принцип единственной ответственности Централизует логику взаимодействия между компонентами в посреднике.
Принцип открытости/закрытости Позволяет добавлять новых посредников без изменения существующих компонентов.
Снижает связанность Уменьшает прямые зависимости между компонентами.
Легко повторно использовать Отдельные компоненты можно легче повторно использовать в разных контекстах.

Недостатки

Недостатки Описание
Посредник может стать сложным По мере роста логики взаимодействия посредник может превратиться в божественный объект.
Накладные расходы на производительность Вся коммуникация проходит через посредника, что может создать узкое место по производительности.
Сложно отлаживать Логика взаимодействия сосредоточена в посреднике, поэтому отладка может потребовать отслеживания множества компонентов.

Лучшие практики и рекомендации

1. Разумно разделяйте обязанности посредника

Не позволяйте одному медиатору обрабатывать всё; можно разделить несколько медиаторов по бизнес-доменам:

Пример

class UserRegistrationMediator:
    def notify(self, sender, event, data):
        if event == "user_registered":
            # Обработка логики после регистрации пользователя
            self.send_welcome_email(data)
            self.create_user_profile(data)
            self.add_to_mailing_list(data)

class OrderProcessingMediator:
    def notify(self, sender, event, data):
        if event == "order_created":
            # Обработка логики после создания заказа
            self.validate_order(data)
            self.process_payment(data)
            self.update_inventory(data)

2. Избегайте чрезмерно сложного посредника

Если посредник становится слишком сложным, рассмотрите возможность использования других паттернов или рефакторинга:

Пример

# Плохая практика — посредник содержит слишком много логики
class ComplexMediator:
    def handle_event(self, event_type, data):
        if event_type == "user_action":
            # Много сложной бизнес-логики...
            pass
        elif event_type == "system_event":
            # Ещё более сложная логика...
            pass

# Хорошая практика — используйте паттерн «Стратегия» для декомпозиции сложной логики
class EventHandler:
    def handle(self, data):
        pass

class UserActionHandler(EventHandler):
    def handle(self, data):
        # Специально обрабатывать действия пользователя
        pass

class SimpleMediator:
    def __init__(self):
        self.handlers = {}
   
    def register_handler(self, event_type, handler):
        self.handlers[event_type] = handler
   
    def notify(self, event_type, data):
        if event_type in self.handlers:
            self.handlers[event_type].handle(data)

3. Рассмотрите объединение с паттерном «Наблюдатель»

Паттерн «Посредник» часто используется в сочетании с паттерном «Наблюдатель»:

Пример

from abc import ABC, abstractmethod
from typing import List

class Observer(ABC):
    @abstractmethod
    def update(self, event, data):
        pass

class Observable:
    def __init__(self):
        self._observers: List[Observer] = []
   
    def add_observer(self, observer):
        self._observers.append(observer)
   
    def notify_observers(self, event, data=None):
        for observer in self._observers:
            observer.update(event, data)

class ChatMediator(Observable):
    def send_message(self, message, sender, receiver=Отсутствует):
        # Логика посредника
        event_data = {
            "message": message,
            "sender": sender,
            "receiver": receiver
        }
        self.notify_observers("message_sent", event_data)

class LoggingObserver(Observer):
    def update(self, event, data):
        if event == "message_sent":
            print(fЛог: {data['sender']} отправил сообщение)

Практические упражнения

Упражнение 1: Улучшение системы чата

Расширьте предыдущую систему чата и добавьте следующие возможности:

  1. Поддержка создания нескольких комнат чата
  2. Поддержка переключения пользователей между разными комнатами чата.
  3. Добавьте функции администратора (исключение пользователей, заглушение и т. д.)

Упражнение 2: Проектирование системы бронирования авиабилетов

Используйте паттерн «Посредник» для проектирования системы бронирования авиабилетов, включающей следующие компоненты:

  • Поиск рейсов
  • Выбор места
  • Обработка платежей
  • Генерация билета
  • Отправка уведомлений

Компоненты должны работать согласованно через посредника.

Упражнение 3: Система управления умным домом

Спроектируйте систему управления умным домом, включающую:

  • Управление освещением
  • Регулирование температуры
  • Мониторинг безопасности
  • Режим сцены

Реализуйте интеллектуальное взаимодействие между устройствами через посредника.


Итог

Паттерн Посредник — мощный инструмент для обработки сложных взаимодействий объектов. Он упрощает архитектуру системы, вводя координационный центр, и особенно подходит для следующих сценариев:

  • Между объектами существуют сложные сетевые взаимодействия
  • Когда необходимо централизованно управлять логикой взаимодействия между несколькими объектами
  • Стремление снизить связанность между объектами

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

Ключевые моменты:

  • Посредник инкапсулирует логику взаимодействия между объектами
  • Компоненты взаимодействуют только с посредником и не зависят напрямую друг от друга
  • Посредник может стать сложным и требует тщательного проектирования
  • Сочетание с другими паттернами (например, Наблюдатель) может дать лучшие результаты
Другие расширения