Patron de médiateur Python

Le patron Médiateur est un patron de conception comportemental qui réduit les dépendances de communication directe entre les objets en introduisant un objet médiateur. Imaginez une tour de contrôle d'aéroport dans la vie réelle : tous les avions ne communiquent pas directement entre eux, mais coordonnent les décollages, les atterrissages et les itinéraires via la tour de contrôle, évitant ainsi le chaos et les conflits.

Dans le développement logiciel, lorsqu'il existe des relations d'interaction complexes entre plusieurs objets, le patron Médiateur peut encapsuler ces interactions dans le médiateur, réduisant ainsi le couplage entre les objets.

Idée centrale

L'idée centrale du patron Médiateur est« Ne parlez pas directement ; coordonnez-vous via un médiateur. »Il résout la complexité des interactions plusieurs-à-plusieurs entre les objets, ce qui rend le système plus facile à maintenir et à étendre.


Pourquoi avons-nous besoin du patron Médiateur ?

Scénario problématique

Supposons que nous développions un système de salon de discussion avec plusieurs utilisateurs qui doivent s'envoyer des messages entre eux. Sans médiateur, le code pourrait ressembler à ceci :

Exemple

class User:
    def __init__(self, name):
        self.name = name
        self.other_users = []  # Il faut connaître tous les autres utilisateurs
   
    def send_message(self, message, to_user):
        print(f"{self.name} a envoyé à {to_user.name} : {message}")
        to_user.receive_message(message, self)
   
    def receive_message(self, message, from_user):
        print(f{self.name} a reçu un message de {from_user.name} : {message})

# Exemple d'utilisation
user1 = User(Xiaoming)
user2 = User(Xiaohong)
user3 = User(Xiaogang)

# Chaque utilisateur doit connaître tous les autres utilisateurs
user1.other_users = [user2, user3]
user2.other_users = [user1, user3]
user3.other_users = [user1, user2]

user1.send_message(Bonjour !, user2)

Inconvénients de cette conception :

  • Couplage élevé entre les objets ; chaque utilisateur doit connaître tous les autres utilisateurs.
  • L'ajout d'un nouvel utilisateur nécessite de modifier tous les utilisateurs existants.
  • Difficile à maintenir ; la logique d'interaction est dispersée entre les objets.

Solution

Le patron Médiateur résout ce problème en introduisant un salon de discussion (médiateur) :

Exemple

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=Aucune):
        if to_user_name:  # Chat privé
            if to_user_name in self.users:
                self.users[to_user_name].receive_message(message, from_user)
        else:  # Chat de groupe
            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} a reçu un message de {from_user.name} : {message})

# Exemple d'utilisation
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(Bonjour tout le monde !)  # Message diffusé
user2.send_message(Bonjour Xiaoming !, Xiaoming)  # Chat privé

Implémentation du patron Médiateur

Structure de base

Comprenons l'implémentation du patron Médiateur à travers un exemple plus complet :

Exemple

from abc import ABC, abstractmethod
from typing import List

# Médiateur abstrait
class Mediator(ABC):
    @abstractmethod
    def notify(self, sender: object, event: str, data: dict = None):
        pass

# Médiateur concret - Salon de discussion
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=Aucune):
        if event == "send_message":
            message = data.get("message")
            target_user = data.get("target_user")
           
            if target_user:  # Discussion privée
                for user in self.users:
                    if user.name == target_user:
                        user.receive_message(message, sender.name)
            else:  # Chat de groupe
                for user in self.users:
                    if user != sender:
                        user.receive_message(message, sender.name)
       
        elif event == "user_joined":
            message = fSystème: {sender.name} a rejoint le salon de discussion
            for user in self.users:
                if user != sender:
                    user.receive_message(message, System)

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

# Composant concret - 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} a envoyé un message : {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} a reçu un message de {from_name} : {message})

# Exemple d'utilisation
def main():
    # Créer le médiateur
    chat_room = ChatRoomMediator()
   
    # Créer un utilisateur
    alice = User("Alice")
    bob = User("Bob")
    charlie = User("Charlie")
   
    # Enregistrer les utilisateurs dans le salon de discussion
    chat_room.add_user(alice)
    chat_room.add_user(bob)
    chat_room.add_user(charlie)
   
    print(=== Démo du salon de discussion ===)
   
    # Alice rejoint le salon de discussion
    alice.join_chat()
   
    # Envoyer un message
    alice.send_message(Bonjour à tous, je suis Alice !)
    bob.send_message(Bienvenue Alice !)
    charlie.send_message(Bonjour Alice !, "Alice")  # Discussion privée
   
    # Bob envoie un message de groupe
    bob.send_message(Y a-t-il quelqu'un en ligne ?)

if __name__ == "__main__":
    main()

Sortie

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

Structure UML du patron Médiateur

Description de la structure

  • Médiateur (interface médiateur): définit l'interface de communication
  • ConcreteMediator (médiateur concret): implémente la logique de coordination et connaît tous les composants
  • BaseComponent (composant de base): contient une référence au médiateur
  • ConcreteComponent (composant concret): implémente la logique métier concrète

Scénarios d'application concrets

Scénario 1 : Application GUI

Dans une interface utilisateur graphique, divers contrôles (boutons, zones de texte, cases à cocher, etc.) coordonnent les interactions par l'intermédiaire d'un médiateur :

Exemple

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":
            # Lorsque le nom d'utilisateur ou le mot de passe est saisi, vérifier si le bouton de connexion peut être activé
            username = self.username_input.get_text()
            password = self.password_input.get_text()
            self.login_button.set_enabled(bool(username and mot de passe))
       
        elif event == "login_clicked":
            # Gérer la logique de connexion
            username = self.username_input.get_text()
            password = self.password_input.get_text()
            remember = self.remember_checkbox.is_checked()
            print(fConnexion : {username}, Se souvenir de moi : {remember})
       
        elif event == "remember_changed":
            print(L'option Se souvenir de moi a changé)

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(fButton {'Activer' if enabled else 'Désactiver'})

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

# Exemple d'utilisation
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)

# Simuler la saisie utilisateur
username_input.set_text("user123")
password_input.set_text("pass123")
login_btn.click()

Scénario 2 : Système de commande e-commerce

Dans un système de commerce électronique, le traitement des commandes implique plusieurs sous-systèmes tels que les stocks, le paiement et la logistique :

Exemple

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(=== Début du traitement de la commande ===)
       
        # Vérifier l'inventaire
        if not self.inventory_system.check_stock(order_data):
            return Stock insuffisant
       
        # Traiter le paiement
        payment_result = self.payment_system.process_payment(order_data)
        if not payment_result:
            return Paiement échoué
       
        # Mettre à jour l'inventaire
        self.inventory_system.update_stock(order_data)
       
        # Organiser l'expédition
        shipping_info = self.shipping_system.schedule_delivery(order_data)
       
        # Envoyer une notification
        self.notification_system.send_confirmation(order_data, shipping_info)
       
        return Commande traitée avec succès

class InventorySystem:
    def check_stock(self, order_data):
        print(Vérification de l'inventaire...)
        return True
   
    def update_stock(self, order_data):
        print(Mise à jour de l'inventaire...)

class PaymentSystem:
    def process_payment(self, order_data):
        print(Traitement du paiement...)
        return True

class ShippingSystem:
    def schedule_delivery(self, order_data):
        print(Organisation de l'expédition...)
        return Numéro de suivi : SF123456789

class NotificationSystem:
    def send_confirmation(self, order_data, shipping_info):
        print(fEnvoyer un e-mail de confirmation, informations de livraison : {shipping_info})

# Exemple d'utilisation
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(fRésultat de la commande : {result})

Avantages et inconvénients du patron Médiateur

Avantages

Avantages Description
Principe de responsabilité unique Centralise la logique d'interaction entre les composants dans le médiateur.
Principe ouvert/fermé Permet d'introduire de nouveaux médiateurs sans modifier les composants existants.
Réduit le couplage Réduit les dépendances directes entre les composants.
Facile à réutiliser Les composants individuels peuvent être réutilisés plus facilement dans différents contextes.

Inconvénients

Inconvénients Description
Le médiateur peut devenir complexe À mesure que la logique d'interaction augmente, le médiateur peut devenir un objet omniscient.
Surcoût de performance Toute communication passe par le médiateur, ce qui peut introduire un goulot d'étranglement de performance.
Difficile à déboguer La logique d'interaction est concentrée dans le médiateur, donc le débogage peut nécessiter de suivre plusieurs composants.

Bonnes pratiques et considérations

1. Répartir raisonnablement les responsabilités du médiateur

Ne laissez pas un seul médiateur tout gérer ; vous pouvez répartir plusieurs médiateurs selon les domaines métier :

Exemple

class UserRegistrationMediator:
    def notify(self, sender, event, data):
        if event == "user_registered":
            # Gérer la logique après l'inscription de l'utilisateur
            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":
            # Gérer la logique après la création de la commande
            self.validate_order(data)
            self.process_payment(data)
            self.update_inventory(data)

2. Éviter un médiateur trop complexe

Si le médiateur devient trop complexe, envisagez d'utiliser d'autres motifs ou de refactoriser :

Exemple

# Mauvaise pratique - le médiateur contient trop de logique
class ComplexMediator:
    def handle_event(self, event_type, data):
        if event_type == "user_action":
            # Beaucoup de logique métier complexe...
            pass
        elif event_type == "system_event":
            # Logique plus complexe...
            pass

# Bonne pratique - utiliser le patron de stratégie pour décomposer une logique complexe
class EventHandler:
    def handle(self, data):
        pass

class UserActionHandler(EventHandler):
    def handle(self, data):
        # Gérer spécifiquement les actions utilisateur
        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. Envisagez de combiner avec le patron Observateur

Le patron Médiateur est souvent utilisé en combinaison avec le patron Observateur :

Exemple

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=Aucune):
        # Logique du médiateur
        event_data = {
            "message": message,
            "sender": expéditeur,
            "receiver": destinataire
        }
        self.notify_observers("message_sent", event_data)

class LoggingObserver(Observer):
    def update(self, event, data):
        if event == "message_sent":
            print(fLog : {data['sender']} a envoyé un message)

Exercices pratiques

Exercice 1 : Améliorer un système de salon de discussion

Étendre le système de salon de discussion précédent et ajouter les fonctionnalités suivantes :

  1. Prendre en charge la création de plusieurs salons de discussion
  2. Prendre en charge le changement d'utilisateurs entre différents salons de discussion.
  3. Ajouter des fonctions d'administrateur (exclusion d'utilisateurs, mise en sourdine, etc.)

Exercice 2 : Concevoir un système de réservation de billets d'avion

Utilisez le patron Médiateur pour concevoir un système de réservation de billets d'avion, comprenant les composants suivants :

  • Recherche de vol
  • Sélection des sièges
  • Traitement des paiements
  • Génération des billets
  • Envoi de notifications

Les composants doivent fonctionner en coordination via le médiateur.

Exercice 3 : Système de contrôle de maison intelligente

Concevez un système de contrôle de maison intelligente, comprenant :

  • Contrôle de l'éclairage
  • Régulation de la température
  • Surveillance de sécurité
  • Mode scène

Implémenter une liaison intelligente entre appareils via le médiateur.


Résumé

Le patron Médiateur est un outil puissant pour gérer des interactions complexes entre objets. Il simplifie l’architecture système en introduisant un centre de coordination, et convient particulièrement aux scénarios suivants :

  • Des relations d’interaction en réseau complexes existent entre les objets
  • Lorsqu’il est nécessaire de contrôler de manière centralisée la logique d’interaction entre plusieurs objets
  • Espérer réduire le couplage entre les objets

Rappelez-vous, le patron Médiateur n’est pas une solution miracle. Lorsque la logique d’interaction est simple, une communication directe d’objet à objet peut être plus appropriée. Toutefois, face à des systèmes complexes, une utilisation appropriée du patron Médiateur peut considérablement améliorer la maintenabilité et l’extensibilité du code.

Points clés :

  • Le médiateur encapsule la logique d’interaction entre les objets
  • Les composants communiquent uniquement avec le médiateur et ne dépendent pas directement les uns des autres
  • Le médiateur peut devenir complexe et nécessite une conception soignée
  • La combinaison avec d’autres patrons (comme Observer) peut donner de meilleurs résultats
autres extensions