Patrón Mediador en Python

El patrón mediador es un patrón de diseño de comportamiento que reduce la dependencia de comunicación directa entre objetos mediante la introducción de un objeto mediador. Imagina la torre de control de un aeropuerto en la vida real: todos los aviones no se comunican directamente entre sí, sino que coordinan despegues, aterrizajes y rutas a través de la torre de control, evitando así el caos y los conflictos.

En el desarrollo de software, cuando existe una relación de interacción compleja entre múltiples objetos, el patrón mediador puede encapsular estas interacciones en el mediador, reduciendo así el acoplamiento entre objetos.

Idea central

La idea central del patrón mediador es"No hablar directamente, sino coordinar a través del mediador"Resuelve la complejidad de las interacciones de muchos a muchos entre objetos, lo que hace que el sistema sea más fácil de mantener y ampliar.


¿Por qué se necesita el patrón mediador?

Escenario del problema

Supongamos que estamos desarrollando un sistema de sala de chat y varios usuarios necesitan enviarse mensajes entre sí. Sin un mediador, el código podría ser así:

Ejemplo

class User:
    def __init__(self, name):
        self.name = name
        self.other_users = []  # Necesita conocer a todos los demás usuarios
   
    def send_message(self, message, to_user):
        print(f{self.name} envía a {to_user.name}: {message})
        to_user.receive_message(message, self)
   
    def receive_message(self, message, from_user):
        print(f{self.name} recibe un mensaje de {from_user.name}: {message})

# Ejemplo de uso
user1 = User(Xiaoming)
user2 = User(Xiaohong)
user3 = User(Xiaogang)

# Cada usuario necesita conocer a todos los demás usuarios
user1.other_users = [user2, user3]
user2.other_users = [user1, user3]
user3.other_users = [user1, user2]

user1.send_message("¡Hola!", user2)

Desventajas de este diseño:

  • Alto acoplamiento entre objetos: cada usuario necesita conocer a todos los demás.
  • Al agregar un nuevo usuario, hay que modificar todos los usuarios existentes.
  • Difícil de mantener, la lógica de interacción está dispersa entre los objetos.

Solución

El patrón mediador resuelve este problema introduciendo una sala de chat (mediador):

Ejemplo

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=None):
        if to_user_name:  # Chat privado
            if to_user_name in self.users:
                self.users[to_user_name].receive_message(message, from_user)
        else:  # Chat grupal
            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} recibe un mensaje de {from_user.name}: {message})

# Ejemplo de uso
chat_room = ChatRoom()

user1 = User(Xiaoming)
user2 = User(Xiaohong)
user3 = User(Xiaogang)

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

user1.send_message(¡Hola a todos!)  # Mensaje de difusión
user2.send_message(¡Hola Xiaoming!, Xiaoming)  # Chat privado

Implementación del patrón mediador

Estructura básica

Entendamos la implementación del patrón mediador con un ejemplo más completo:

Ejemplo

from abc import ABC, abstractmethod
from typing import List

# Mediador abstracto
class Mediator(ABC):
    @abstractmethod
    def notify(self, sender: object, event: str, data: dict = None):
        pass

# Mediador concreto - Sala de chat
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=None):
        if event == "send_message":
            message = data.get("message")
            target_user = data.get("target_user")
           
            if target_user:  # Chat privado
                for user in self.users:
                    if user.name == target_user:
                        user.receive_message(message, sender.name)
            else:  # Chat grupal
                for user in self.users:
                    if user != sender:
                        user.receive_message(message, sender.name)
       
        elif event == "user_joined":
            message = fSistema: {sender.name} se unió a la sala de chat
            for user in self.users:
                if user != sender:
                    user.receive_message(message, Sistema)

# Clase de componente base
class BaseComponent:
    def __init__(self):
        self._mediator = None
   
    def set_mediator(self, mediator: Mediator):
        self._mediator = mediator

# Componente concreto - Usuario
class User(BaseComponent):
    def __init__(self, name):
        super().__init__()
        self.name = name
   
    def send_message(self, message, target_user=None):
        print(f{self.name} envía un mensaje: {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} recibe un mensaje de {from_name}: {message})

# Ejemplo de uso
def main():
    # Crear mediador
    chat_room = ChatRoomMediator()
   
    # Crear usuario
    alice = User("Alice")
    bob = User("Bob")
    charlie = User("Charlie")
   
    # Registrar usuario en la sala de chat
    chat_room.add_user(alice)
    chat_room.add_user(bob)
    chat_room.add_user(charlie)
   
    print(=== Demostración de sala de chat ===)
   
    # Alice se une a la sala de chat
    alice.join_chat()
   
    # Enviar mensaje
    alice.send_message(¡Hola a todos, soy Alice!)
    bob.send_message(¡Bienvenida Alice!)
    charlie.send_message(¡Hola Alice!, "Alice")  # Chat privado
   
    # Bob envía un mensaje grupal
    bob.send_message(¿Hay alguien en línea?)

if __name__ == "__main__":
    main()

Resultado de ejecución

=== 聊天室演示 ===
系统: 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 的消息: 有人在线吗?

Estructura UML del patrón mediador

Descripción de la estructura

  • Mediator (interfaz del mediador): define la interfaz de comunicación.
  • ConcreteMediator (mediador concreto): implementa la lógica de coordinación y conoce todos los componentes.
  • BaseComponent (componente base): contiene la referencia al mediador.
  • ConcreteComponent (componente concreto): implementa la lógica de negocio concreta.

Escenarios de aplicación real

Escenario 1: Aplicación de GUI

En una interfaz gráfica de usuario, varios controles (botones, cuadros de texto, casillas de verificación, etc.) coordinan sus interacciones a través de un mediador:

Ejemplo

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":
            # Al introducir el nombre de usuario o la contraseña, comprobar si se puede habilitar el botón de iniciar sesión.
            username = self.username_input.get_text()
            password = self.password_input.get_text()
            self.login_button.set_enabled(bool(username and password))
       
        elif event == "login_clicked":
            # Procesar la lógica de inicio de sesión.
            username = self.username_input.get_text()
            password = self.password_input.get_text()
            remember = self.remember_checkbox.is_checked()
            print(fIniciar sesión: {username}, Recordarme: {remember})
       
        elif event == "remember_changed":
            print(La opción de recordarme ha cambiado)

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

class Button(UIComponent):
    def click(self):
        if self.mediator:
            self.mediator.notify(self, "login_clicked")
   
    def set_enabled(self, enabled):
        print(fBotón {'Habilitar' if enabled else 'Deshabilitar'})

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

# Ejemplo de uso
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)

# Simular la entrada del usuario
username_input.set_text("user123")
password_input.set_text("pass123")
login_btn.click()

Escenario 2: Sistema de pedidos de comercio electrónico

En un sistema de comercio electrónico, el procesamiento de pedidos involucra múltiples subsistemas como inventario, pago, logística, etc.:

Ejemplo

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(=== Iniciar el procesamiento del pedido ===)
       
        # Comprobar el inventario
        if not self.inventory_system.check_stock(order_data):
            return Stock insuficiente
       
        # Procesar el pago
        payment_result = self.payment_system.process_payment(order_data)
        if not payment_result:
            return Error en el pago
       
        # Actualizar el inventario
        self.inventory_system.update_stock(order_data)
       
        # Organizar el envío
        shipping_info = self.shipping_system.schedule_delivery(order_data)
       
        # Enviar notificación
        self.notification_system.send_confirmation(order_data, shipping_info)
       
        return Pedido procesado correctamente

class InventorySystem:
    def check_stock(self, order_data):
        print(Comprobando inventario...)
        return True
   
    def update_stock(self, order_data):
        print(Actualizando inventario...)

class PaymentSystem:
    def process_payment(self, order_data):
        print(Procesando pago...)
        return True

class ShippingSystem:
    def schedule_delivery(self, order_data):
        print(Organizando envío...)
        return Número de seguimiento: SF123456789

class NotificationSystem:
    def send_confirmation(self, order_data, shipping_info):
        print(fEnviar correo de confirmación, información de envío: {shipping_info})

# Ejemplo de uso
mediator = OrderMediator()

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

order_data = {"product_id": "123", "quantity": 2, "user_id": "user001"}
result = mediator.place_order(order_data)
print(fResultado del pedido: {result})

Ventajas y desventajas del patrón mediador

Ventajas

Ventajas Descripción
Principio de responsabilidad única Centraliza la lógica de interacción entre los componentes en el mediador.
Principio abierto/cerrado Se puede introducir un nuevo mediador sin modificar los componentes existentes.
Reduce el acoplamiento Reduce las dependencias directas entre componentes.
Fácil de reutilizar Los componentes individuales se pueden reutilizar más fácilmente en diferentes contextos.

Desventajas

Desventajas Descripción
El mediador puede volverse complejo A medida que aumenta la lógica de interacción, el mediador puede convertirse en un objeto todopoderoso (God Object).
Coste de rendimiento Toda la comunicación pasa a través del mediador, lo que puede introducir un cuello de botella de rendimiento.
Dificultad de depuración La lógica de interacción se concentra en el mediador, por lo que al depurar puede ser necesario rastrear múltiples componentes.

Mejores prácticas y consideraciones

1. Dividir razonablemente las responsabilidades del mediador

No permitas que un mediador maneje todo; se pueden dividir varios mediadores según el dominio de negocio:

Ejemplo

class UserRegistrationMediator:
    def notify(self, sender, event, data):
        if event == "user_registered":
            # Procesar la lógica posterior al registro del usuario
            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":
            # Procesar la lógica posterior a la creación del pedido
            self.validate_order(data)
            self.process_payment(data)
            self.update_inventory(data)

2. Evitar que el mediador sea excesivamente complejo

Si el mediador se vuelve demasiado complejo, considera el uso de otros patrones o refactoriza:

Ejemplo

# Mala práctica: el mediador contiene demasiada lógica
class ComplexMediator:
    def handle_event(self, event_type, data):
        if event_type == "user_action":
            # Gran cantidad de lógica de negocio compleja...
            pass
        elif event_type == "system_event":
            # Más lógica compleja...
            pass

# Buenas prácticas: usar el patrón de estrategia para descomponer la lógica compleja
class EventHandler:
    def handle(self, data):
        pass

class UserActionHandler(EventHandler):
    def handle(self, data):
        # Manejar acciones de usuario específicas
        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. Considerar la combinación con el patrón observador

El patrón mediador se usa a menudo junto con el patrón observador:

Ejemplo

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=None):
        # Lógica del mediador
        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"Registro: {data['sender']} envió un mensaje")

Ejercicios

Ejercicio 1: Mejorar el sistema de sala de chat

Extienda el sistema de sala de chat anterior agregando las siguientes funciones:

  1. Soporte para crear múltiples salas de chat
  2. Soporte para que los usuarios cambien entre diferentes salas de chat
  3. Agregar funciones de administrador (expulsar usuarios, silenciar, etc.)

Ejercicio 2: Diseñar un sistema de reserva de billetes de avión

Diseñe un sistema de reserva de boletos de avión usando el patrón mediador, que incluya los siguientes componentes:

  • Consulta de vuelos
  • Selección de asientos
  • Procesamiento de pagos
  • Generación de boletos
  • Envío de notificaciones

Se requiere que los componentes trabajen de manera coordinada a través del mediador.

Ejercicio 3: Sistema de control de hogar inteligente

Diseñe un sistema de control para hogar inteligente que incluya:

  • Control de iluminación
  • Regulación de temperatura
  • Monitoreo de seguridad
  • Modos de escena

Implemente la interconexión inteligente entre dispositivos a través del mediador.


Resumen

El patrón mediador es una herramienta poderosa para manejar interacciones complejas entre objetos. Introduce un centro de coordinación para simplificar la arquitectura del sistema, y es especialmente adecuado para los siguientes escenarios:

  • Existen relaciones de interacción complejas en malla entre objetos.
  • Se necesita controlar de manera centralizada la lógica de interacción entre múltiples objetos.
  • Se desea reducir el acoplamiento entre objetos.

Recuerde que el patrón mediador no es una solución universal. Cuando la lógica de interacción es simple, la comunicación directa entre objetos puede ser más adecuada. Sin embargo, al tratar con sistemas complejos, el uso razonable del patrón mediador puede mejorar significativamente la mantenibilidad y extensibilidad del código.

Puntos clave:

  • El mediador encapsula la lógica de interacción entre objetos.
  • Los componentes solo se comunican con el mediador y no dependen directamente entre sí.
  • El mediador puede volverse complejo, por lo que requiere un diseño razonable.
  • Combinarlo con otros patrones (como el observador) puede producir mejores resultados.
Otras extensiones