Patron Pont en Python

Le patron Bridge est un patron de conception structurel qui sépare l’abstraction de l’implémentation afin qu’elles puissent varier indépendamment. En termes simples, il consiste àséparer les multiples dimensions variables d’une chose, permettant à chaque dimension d’être étendue indépendamment.。

Métaphore du patron Bridge dans la vie quotidienne

Imaginez la relation entre une télécommande et un téléviseur :

  • Télécommande(partie abstraction) : fournit une interface de contrôle (alimentation, volume, chaîne)
  • Téléviseur(partie implémentation) : effectue réellement les opérations (Sony TV, Samsung TV, Xiaomi TV)

La télécommande n'a pas besoin de savoir de quel téléviseur il s'agit, et le téléviseur n'a pas besoin de savoir de quelle télécommande il s'agit. Ils sont reliés par un "pont" et peuvent être mis à niveau indépendamment.


Pourquoi avons-nous besoin du patron de conception Bridge ?

Limites de l'approche traditionnelle

Supposons que nous voulions concevoir un système de dessin graphique qui prend en charge différentes formes et couleurs :

Exemple

# Implémentation traditionnelle - problème d'explosion de classes
class RedCircle:
    def draw(self):
        print("Draw red circle")

class BlueCircle:
    def draw(self):
        print("Dessiner un cercle bleu")

class RedRectangle:
    def draw(self):
        print("Dessiner un rectangle rouge")

class BlueRectangle:
    def draw(self):
        print("Dessiner un rectangle bleu")

# Chaque fois qu'une couleur ou une forme est ajoutée, une nouvelle classe doit être créée
# 3 couleurs × 3 formes = 9 classes !

Avantages du patron Bridge

Exemple

# Implémentation du patron Bridge
class Color:
    """Interface Color - partie d'implémentation"""
    def apply_color(self):
        pass

class Shape:
    """Abstraction de Shape - partie abstraction"""
    def __init__(self, color):
        self.color = color  # La clé du pontage !
   
    def draw(self):
        pass

# Il suffit de : 3 couleurs + 3 formes = 6 classes
# Au lieu de : 3 × 3 = 9 classes

Structure centrale du patron Pont

Comprenons les composants du patron Pont à travers un exemple complet :

Quatre rôles clés

  1. Abstraction: définit l'interface abstraite et maintient une référence à la partie implémentation
  2. Abstraction raffinée: étend l'interface abstraite
  3. Implémenteur: définit l'interface d'implémentation
  4. Implémenteur concret: implémente concrètement l'interface

Exemple de code complet : Système de dessin graphique

Étape 1 : Définir l'interface de la partie implémentation (Color)

Exemple

from abc import ABC, abstractmethod

class Color(ABC):
    """Interface Color - partie d'implémentation"""
    @abstractmethod
    def apply_color(self):
        pass

class Red(Color):
    """Implémentation concrète - Red"""
    def apply_color(self):
        return "Red"

class Blue(Color):
    """Implémentation concrète - Blue"""
    def apply_color(self):
        return "Blue"

class Green(Color):
    """Implémentation concrète - Vert"""
    def apply_color(self):
        return "Vert"

Étape 2 : Définir la partie abstraction (Shape)

Exemple

class Shape(ABC):
    """Abstraction de Shape - partie abstraction"""
    def __init__(self, color: Color):
        self.color = color  # La clé de la liaison : garder une référence à la partie implémentation
   
    @abstractmethod
    def draw(self):
        pass

class Circle(Shape):
    """Abstraction concrète - Cercle"""
    def draw(self):
        color_name = self.color.apply_color()
        print(f"Dessiner un cercle en {color_name}")

class Rectangle(Shape):
    """Abstraction concrète - Rectangle"""
    def draw(self):
        color_name = self.color.apply_color()
        print(f"Dessiner un rectangle en {color_name}")

class Triangle(Shape):
    """Abstraction concrète - Triangle"""
    def draw(self):
        color_name = self.color.apply_color()
        print(f"Dessiner un triangle en {color_name}")

Étape 3 : Utiliser le patron Bridge

Exemple

# Code client
def main():
    # Créer l'implémentation de couleur
    red = Rouge()
    blue = Bleu()
    green = Vert()
   
    # Créer l'abstraction de forme et relier l'implémentation de couleur
    red_circle = Circle(red)
    blue_rectangle = Rectangle(blue)
    green_triangle = Triangle(green)
   
    # Dessiner la forme
    print("=== Résultats du dessin ===")
    red_circle.draw()        # Sortie : Dessiner un cercle rouge
    blue_rectangle.draw()    # Sortie : Dessiner un rectangle bleu
    green_triangle.draw()    # Sortie : Dessiner un triangle vert
   
    # Combinaison flexible : rectangle rouge
    red_rectangle = Rectangle(red)
    red_rectangle.draw()     # Sortie : Dessiner un rectangle rouge

if __name__ == "__main__":
    main()

Scénarios d'application pratiques

Scénario 1 : Système d'envoi de messages

Exemple

from abc import ABC, abstractmethod

# Partie d'implémentation : méthode d'envoi de messages
class MessageSender(ABC):
    @abstractmethod
    def send(self, message):
        pass

class EmailSender(MessageSender):
    def send(self, message):
        return f"Envoyer par e-mail : {message}"

class SMSSender(MessageSender):
    def send(self, message):
        return f"Envoyer par SMS : {message}"

class WeChatSender(MessageSender):
    def send(self, message):
        return f"Envoyer via WeChat : {message}"

# Partie abstraction : type de message
class Message(ABC):
    def __init__(self, sender: MessageSender):
        self.sender = sender
   
    @abstractmethod
    def send(self):
        pass

class UrgentMessage(Message):
    def send(self):
        message = "[Urgent] " + self.get_content()
        return self.sender.send(message)
   
    def get_content(self):
        return "Une exception système s'est produite, veuillez la traiter immédiatement !"

class NormalMessage(Message):
    def __init__(self, sender: MessageSender, content):
        super().__init__(sender)
        self.content = content
   
    def send(self):
        return self.sender.send(self.content)

# Exemple d'utilisation
def message_example():
    email_sender = EmailSender()
    sms_sender = SMSSender()
   
    # Message urgent envoyé par SMS
    urgent_sms = UrgentMessage(sms_sender)
    print(urgent_sms.send())  # Envoyer par SMS : [Urgent] Une exception système s'est produite, veuillez la traiter immédiatement !
   
    # Message normal envoyé par e-mail
    normal_email = NormalMessage(email_sender, "Le rapport mensuel a été généré")
    print(normal_email.send())  # Envoyer par e-mail : Le rapport mensuel a été généré

message_example()

Scénario 2 : Système de contrôle à distance d'appareils

Exemple

# Partie implémentation : appareil
class Device(ABC):
    @abstractmethod
    def turn_on(self):
        pass
   
    @abstractmethod
    def turn_off(self):
        pass
   
    @abstractmethod
    def set_volume(self, volume):
        pass

class TV(Device):
    def turn_on(self):
        return "La TV est allumée"
   
    def turn_off(self):
        return "La TV est éteinte"
   
    def set_volume(self, volume):
        return f"Volume de la TV réglé à : {volume}"

class Radio(Device):
    def turn_on(self):
        return "La radio est allumée"
   
    def turn_off(self):
        return "La radio est éteinte"
   
    def set_volume(self, volume):
        return f"Volume de la radio réglé à : {volume}"

# Partie abstraction : télécommande
class RemoteControl:
    def __init__(self, device: Device):
        self.device = device
   
    def toggle_power(self):
        return "Basculer l'état d'alimentation"
   
    def volume_up(self):
        return "Augmenter le volume"
   
    def volume_down(self):
        return "Baisser le volume"

class AdvancedRemoteControl(RemoteControl):
    def mute(self):
        return "Mode silencieux"
   
    def set_channel(self, channel):
        return f"Passer à la chaîne : {channel}"

# Exemple d'utilisation
def remote_example():
    tv = TV()
    radio = Radio()
   
    basic_remote = RemoteControl(tv)
    advanced_remote = AdvancedRemoteControl(radio)
   
    print(basic_remote.toggle_power())  # Basculer l'état d'alimentation
    print(advanced_remote.mute())       # Mode silencieux

remote_example()

Comparaison des avantages du patron Bridge

Fonctionnalité Approche traditionnelle Patron Pont
Évolutivité Médiocre, nécessite de modifier le code existant Bon, peut être étendu indépendamment
Nombre de classes Nombreuses, propices à l'explosion des classes Peu nombreux, croît linéairement
Kemudahan pemeliharaan Difficile, un petit changement affecte tout Facile, séparation des préoccupations
Flexibilité Combinaisons fixes Composition dynamique à l'exécution

Bonnes pratiques et considérations

Quand l'utiliser

✅ Situations adaptées à l'utilisation du patron Bridge :

  • Lorsque vous devez étendre indépendamment selon plusieurs dimensions
  • Lorsque vous devez changer d'implémentation à l'exécution
  • Lorsque vous devez éviter une liaison permanente entre l'abstraction et l'implémentation
  • Lorsque l'héritage rendrait la hiérarchie de classes trop complexe

❌ Situations où il n'est pas adapté :

  • Lorsqu'il y a peu de dimensions variables et qu'aucune conception complexe n'est nécessaire
  • Lorsqu'il existe une relation de couplage forte entre l'abstraction et l'implémentation

Erreurs courantes et comment les éviter

Exemple

# Exemple incorrect : pas vraiment découplé
class BadShape:
    def __init__(self, color_type):
        if color_type == "red":
            self.color = Red()
        elif color_type == "blue":
            self.color = Blue()
        # Il faut encore modifier cette classe pour ajouter une nouvelle couleur

# Approche correcte : injection de dépendances
class GoodShape:
    def __init__(self, color: Color):  # Dépendre des interfaces, pas des implémentations concrètes
        self.color = color

Exercices pratiques

Exercice 1 : Étendre le système graphique

Veuillez ajouter les fonctionnalités suivantes au système graphique :

  1. Ajouter une nouvelle couleur (jaune)
  2. Ajouter une nouvelle forme (ellipse)
  3. Créer une ellipse jaune et la dessiner

Exercice 2 : Concevoir un système de paiement

Concevoir un système de paiement avec les exigences suivantes :

  • Moyens de paiement : Alipay, WeChat Pay, carte bancaire
  • Types de paiement : paiement normal, paiement échelonné, paiement combiné
  • Implémenté à l'aide du modèle Pont pour garantir que les méthodes de paiement et les types de paiement peuvent être étendus indépendamment

Résumé

Le modèle Pont, enséparant l'abstraction et l'implémentation,offre une grande flexibilité. Son idée centrale est :

  1. Identifier les dimensions de variation: Trouver plusieurs dimensions dans le système qui peuvent changer indépendamment
  2. Établir un pont: Connecter différentes dimensions par des relations de composition
  3. Expansion indépendante: Chaque dimension peut se développer et changer indépendamment
Autres extensions