Patron Monteur en Python

Le modèle Builder nous permet de créer différentes représentations d'objets en utilisant le même processus de construction. Imaginez la construction d'une maison : que vous construisiez une villa ou un appartement, vous devez passer par des étapes telles que la pose des fondations, la construction des murs et l'installation de la plomberie et des systèmes électriques, mais les résultats finaux sont complètement différents.

Idée centrale

Le modèle Builder sépare le processus de construction d'un objet complexe de sa représentation, permettant au même processus de construction de créer différentes représentations. Ce modèle est particulièrement adapté à la création d'objets complexes composés de plusieurs composants.


Pourquoi avons-nous besoin du modèle Builder ?

Problèmes avec les méthodes de construction traditionnelles

Regardons d'abord un exemple qui n'utilise pas le modèle Builder :

Exemple

class Computer:
    def __init__(self, cpu, memory, storage, graphics_card, monitor):
        self.cpu = cpu
        self.memory = memory
        self.storage = storage
        self.graphics_card = graphics_card
        self.monitor = monitor
   
    def __str__(self):
        return f"Ordinateur : CPU={self.cpu}, Mémoire={self.memory}GB, Stockage={self.storage}GB, Graphiques={self.graphics_card}, Moniteur={self.monitor}"

# Créer un objet ordinateur
computer = Computer("Intel i7", 16, 512, "NVIDIA RTX 3060", "27 pouces 4K")
print(computer)

Inconvénients de cette approche :

  • Trop de paramètres de constructeur, ce qui rend la maintenance difficile
  • Il faut se souvenir de l'ordre de tous les paramètres
  • Le processus de création n'est pas assez flexible
  • Mauvaise lisibilité du code

Composants du patron Builder

Le patron Builder implique quatre rôles principaux :

1. Product

L'objet complexe à créer

2. Builder (Builder abstrait)

Définit l'interface abstraite pour créer les différentes parties d'un produit

3. ConcreteBuilder (Builder concret)

Implémente l'interface Builder, construisant et assemblant les différentes parties

4. Directeur

Construit un objet qui utilise l'interface Builder


Implémentation complète du code

Implémentons entièrement le patron Builder avec un exemple de configuration d'ordinateur:

1. Classe Produit

Exemple

class Computer:
    """Classe Produit Computer"""
    def __init__(self):
        self.cpu = None
        self.memory = None
        self.storage = None
        self.graphics_card = None
        self.monitor = None
   
    def __str__(self):
        specs = []
        if self.cpu:
            specs.append(f"Processeur : {self.cpu}")
        if self.memory:
            specs.append(f"Mémoire : {self.memory}GB")
        if self.storage:
            specs.append(f"Stockage : {self.storage}GB")
        if self.graphics_card:
            specs.append(f"Carte graphique : {self.graphics_card}")
        if self.monitor:
            specs.append(f"Écran : {self.monitor}")
       
        return "Configuration de l'ordinateur :\n" + "\n".join(f" - {spec}" for spec in specs)

2. Monteur abstrait (Builder)

Exemple

from abc import ABC, abstractmethod

class ComputerBuilder(ABC):
    """Classe abstraite de constructeur d'ordinateur"""
   
    def __init__(self):
        self.computer = Computer()
   
    @abstractmethod
    def build_cpu(self):
        pass
   
    @abstractmethod
    def build_memory(self):
        pass
   
    @abstractmethod
    def build_storage(self):
        pass
   
    @abstractmethod
    def build_graphics_card(self):
        pass
   
    @abstractmethod
    def build_monitor(self):
        pass
   
    def get_computer(self):
        return self.computer

3. ConcreteBuilder (Builder concret)

Exemple

class GamingComputerBuilder(ComputerBuilder):
    """Constructeur d'ordinateur de jeu"""
   
    def build_cpu(self):
        self.computer.cpu = "Intel i9-13900K"
   
    def build_memory(self):
        self.computer.memory = 32
   
    def build_storage(self):
        self.computer.storage = 2000  # 2TB
   
    def build_graphics_card(self):
        self.computer.graphics_card = "NVIDIA RTX 4090"
   
    def build_monitor(self):
        self.computer.monitor = "32-inch 4K 144Hz"

class OfficeComputerBuilder(ComputerBuilder):
    """Constructeur d'ordinateur de bureau"""
   
    def build_cpu(self):
        self.computer.cpu = "Intel i5-13400"
   
    def build_memory(self):
        self.computer.memory = 16
   
    def build_storage(self):
        self.computer.storage = 512
   
    def build_graphics_card(self):
        self.computer.graphics_card = "Graphiques intégrés"
   
    def build_monitor(self):
        self.computer.monitor = "24-inch 1080P"

4. Directeur

Exemple

class ComputerDirector:
    """Directeur de construction d'ordinateur"""
   
    def __init__(self, builder):
        self.builder = builder
   
    def construct_computer(self):
        """Le processus complet de construction d'un ordinateur"""
        self.builder.build_cpu()
        self.builder.build_memory()
        self.builder.build_storage()
        self.builder.build_graphics_card()
        self.builder.build_monitor()
   
    def get_computer(self):
        return self.builder.get_computer()

Exemple d'utilisation

Voyons maintenant comment utiliser le patron Builder pour créer différents types d'ordinateurs :

Exemple

def main():
    print("=== Démo du patron Builder ===\n"\n")
   
    # Créer un ordinateur de jeu
    print("1. Construction de l'ordinateur de jeu :")
    gaming_builder = GamingComputerBuilder()
    director = ComputerDirector(gaming_builder)
    director.construct_computer()
    gaming_computer = director.get_computer()
    print(gaming_computer)
   
    print("\n" + "="*50 + "\n")
   
    # Créer un ordinateur de bureau
    print("2. Construction de l'ordinateur de bureau :")
    office_builder = OfficeComputerBuilder()
    director = ComputerDirector(office_builder)
    director.construct_computer()
    office_computer = director.get_computer()
    print(office_computer)
   
    print("\n" + "="*50 + "\n")
   
    # Créer un ordinateur personnalisé
    print("3. Assemblage d'un ordinateur personnalisé :")
    custom_builder = CustomComputerBuilder()
    custom_builder.build_cpu("AMD Ryzen 7 7800X3D")
    custom_builder.build_memory(64)
    custom_builder.build_storage(4000)
    custom_builder.build_graphics_card("AMD RX 7900 XTX")
    custom_builder.build_monitor("moniteur incurvé ultra-large de 34 pouces")
    custom_computer = custom_builder.get_computer()
    print(custom_computer)

# Constructeur personnalisé (construction avec des étapes optionnelles)
class CustomComputerBuilder:
    """Constructeur d'ordinateur personnalisé"""
   
    def __init__(self):
        self.computer = Computer()
   
    def build_cpu(self, cpu):
        self.computer.cpu = cpu
        return self  # Renvoyer self pour prendre en charge les appels chaînés
   
    def build_memory(self, memory):
        self.computer.memory = memory
        return self
   
    def build_storage(self, storage):
        self.computer.storage = storage
        return self
   
    def build_graphics_card(self, graphics_card):
        self.computer.graphics_card = graphics_card
        return self
   
    def build_monitor(self, monitor):
        self.computer.monitor = monitor
        return self
   
    def get_computer(self):
        return self.computer

if __name__ == "__main__":
    main()

Variantes du patron Builder

1. Builder à interface fluide

Exemple

class Pizza:
    def __init__(self):
        self.size = None
        self.cheese = False
        self.pepperoni = False
        self.mushrooms = False
   
    def __str__(self):
        ingredients = []
        if self.cheese: ingredients.append("Cheese")
        if self.pepperoni: ingredients.append("Italian sausage")
        if self.mushrooms: ingredients.append("Champignons")
        return f"{self.size}-inch pizza, garnitures : {', '.join(ingredients)}"

class PizzaBuilder:
    def __init__(self, size):
        self.pizza = Pizza()
        self.pizza.size = size
   
    def add_cheese(self):
        self.pizza.cheese = True
        return self
   
    def add_pepperoni(self):
        self.pizza.pepperoni = True
        return self
   
    def add_mushrooms(self):
        self.pizza.mushrooms = True
        return self
   
    def build(self):
        return self.pizza

# Utiliser l'interface fluide
pizza = (PizzaBuilder(12)
         .add_cheese()
         .add_pepperoni()
         .add_mushrooms()
         .build())
print(pizza)

Avantages et inconvénients du patron Builder

Avantages

Avantages Description
Bonne encapsulation Le processus de construction est séparé de la représentation ; les clients n'ont pas besoin de connaître les détails internes.
Forte extensibilité De nouveaux constructeurs concrets peuvent être ajoutés facilement.
Un meilleur contrôle Permet un contrôle plus fin du processus de construction.
Lisibilité du code Le code est plus clair lorsqu'on utilise des appels chaînés.

Inconvénients

Inconvénients Description
Ajoute de la complexité Nécessite de définir plusieurs classes, ce qui augmente la complexité du système.
Ne convient pas lorsque les produits diffèrent fortement Si les produits diffèrent fortement les uns des autres, le patron Builder n'est pas très adapté.
Les Builders dépendent du produit Le Builder doit connaître les détails spécifiques du produit.

Scénarios applicables

Cas adaptés au patron Builder :

  1. Création d'objets complexes: besoin de créer des objets complexes composés de plusieurs parties.
  2. Processus de construction stable: le processus de construction est relativement stable, mais les composants de l'objet changent fréquemment.
  3. Représentations multiples: l'objet à créer possède plusieurs représentations différentes.
  4. Éviter les constructeurs qui se chevauchent: éviter d'utiliser des constructeurs avec de nombreux paramètres.

Exemples d'applications pratiques :

  • Convertisseur de documents: convertir des documents vers différents formats tels que PDF, Word, HTML.
  • Préparation de repas: préparer des repas pour différents ensembles (combinaisons de burgers, frites, boissons).
  • Construction de composants d'interface utilisateur: construire des composants complexes d'interface utilisateur.
  • Génération de rapports: générer des rapports contenant différentes parties (en-tête, données, graphiques).

Exercices pratiques

Exercice 1 : implémenter un constructeur de voiture

Essayez d'implémenter un patron de construction de voiture avec les composants suivants :

  • Moteur (essence, électrique, hybride)
  • Couleur de carrosserie (rouge, bleu, noir, blanc)
  • Taille des roues (16 pouces, 18 pouces, 20 pouces)
  • Intérieur (cuir, tissu)

Exercice 2 : Améliorer le Computer Builder

Ajoutez les fonctionnalités suivantes au Computer Builder :

  • Composants optionnels (par exemple, s'il faut installer un lecteur optique)
  • Logique de validation (par exemple, la mémoire ne peut pas être inférieure à 4GB)
  • Fonction de calcul du prix

Résumé

Le patron Builder est un puissant patron de conception de création. En décomposant le processus de construction d'un objet complexe en plusieurs étapes simples, il rend le processus de création plus flexible et plus contrôlable. Bien qu'il augmente la complexité du système, dans les scénarios où des objets complexes doivent être créés et où le processus de construction est relativement fixe, le patron Builder peut considérablement améliorer la maintenabilité et l'évolutivité du code.

Rappelez-vous l'idée centrale des patrons de conception :N'utilisez pas les patrons pour le simple plaisir de les utiliser, mais pour résoudre des problèmes de conception spécifiques.En développement réel, décidez d'utiliser ou non le patron Builder en fonction des exigences spécifiques.

Autres extensions