Patron Prototype en Python
Imaginez que vous voulez fabriquer un lot de voitures jouets identiques. Vous ne concevriez pas et ne fabriqueriez pas chacune à partir de zéro à chaque fois ; au lieu de cela, vous fabriquez d'abord un prototype parfait, puis vous copiez à partir de ce prototype. C'est l'idée centrale du patron Prototype !
Patron Prototypeest un patron de conception de création qui crée de nouveaux objets en copiant un objet existant (le prototype) plutôt qu'en instanciant via une classe. Cette approche convient particulièrement lorsque le coût de création d'un objet est élevé, ou lorsqu'il faut créer plusieurs objets similaires.
Pourquoi avons-nous besoin du patron Prototype ?
Examinons d'abord quels problèmes existent avec la manière traditionnelle de créer des objets :
Exemple
class Car:
def __init__(self, brand, model, color, engine_type):
self.brand = brand
self.model = model
self.color = color
self.engine_type = engine_type
# Supposons qu'il y ait une logique d'initialisation complexe ici
self.initialize_complex_components()
def initialize_complex_components(self):
# Simuler le processus d'initialisation complexe
import time
time.sleep(1) # Supposons que l'initialisation prend 1 seconde
print(f"Initialisation des composants complexes de {self.brand} {self.model}...")
# Créer plusieurs objets similaires
car1 = Car("Toyota", "Camry", "Red", "2.5L")
car2 = Car("Toyota", "Camry", "Blue", "2.5L") # Il faut réexécuter l'initialisation complexe
Inconvénients de la méthode traditionnelle :
- Chaque fois qu'un objet est créé, le processus d'initialisation complet doit être exécuté.
- Si l'initialisation est complexe, elle consommera beaucoup de temps et de ressources.
- Duplication de code, faible efficacité.
Principe d'implémentation du patron Prototype
Le patron Prototype résout les problèmes ci-dessus en laissant l'objet responsable de créer sa propre copie. En Python, nous pouvons utiliser lecopymodule pour implémenter facilement le patron Prototype.
Composants principaux

Mécanisme de copie en Python
Python propose deux façons de copier :
| Type de copie | Méthode | Fonctionnalités | Scénarios applicables |
|---|---|---|---|
| Copie superficielle | copy.copy() |
Ne copie que l'objet lui-même, pas les objets imbriqués. | La structure de l'objet est simple, sans références imbriquées. |
| Copie profonde | copy.deepcopy() |
Copie l'objet et tous ses objets imbriqués. | La structure de l'objet est complexe, avec des références imbriquées. |
Implémentation du patron Prototype
Apprenons comment implémenter le patron Prototype à travers un exemple complet.
Implémentation de base
Exemple
from abc import ABC, abstractmethod
from typing import Any
class Prototype(ABC):
"""Classe de base abstraite Prototype"""
@abstractmethod
def clone(self) -> Any:
"""Méthode clone - doit être implémentée par les sous-classes"""
pass
class CarPrototype(Prototype):
"""Classe prototype de voiture"""
def __init__(self, brand: str, model: str, color: str, engine_type: str):
self.brand = brand
self.model = model
self.color = color
self.engine_type = engine_type
self.accessories = [] # Liste des accessoires
self.initialize_complex_components()
def initialize_complex_components(self):
"""Simuler le processus d'initialisation complexe"""
print(f"Initialisation des composants complexes de {self.brand} {self.model}...")
# Ici, vous pouvez simuler certaines opérations d'initialisation chronophages
def add_accessory(self, accessory: str):
"""Ajouter un accessoire"""
self.accessories.append(accessory)
def clone(self) -> 'CarPrototype':
"""Implémenter la méthode clone - utiliser une copie profonde"""
return copy.deepcopy(self)
def display_info(self):
"""Afficher les informations de la voiture"""
info = f"{self.brand} {self.model} - Couleur : {self.color}, Moteur : {self.engine_type}"
if self.accessories:
info += f", Accessoires : {', '.join(self.accessories)}"
print(info)
Exemple d'utilisation
Exemple
print("=== Création d'un objet prototype ===")
original_car = CarPrototype("Toyota", "Camry", "Blanc", "2.5L")
original_car.add_accessory("Système de navigation")
original_car.add_accessory("Toit ouvrant")
original_car.display_info()
print("\n"=== Création de nouveaux objets par clonage ===")
# Créer un nouvel objet basé sur le prototype
car1 = original_car.clone()
car1.color = "Rouge" # Modifier uniquement la couleur
car1.display_info()
car2 = original_car.clone()
car2.color = "Bleu"
car2.add_accessory("Sièges en cuir") # Ajouter un nouvel accessoire
car2.display_info()
# Vérifier que l'objet prototype n'a pas été modifié
print("\n"=== Vérifier que l'objet prototype n'a pas été modifié ===")
original_car.display_info()
Sortie :
=== 创建原型对象 === 正在初始化 Toyota Camry 的复杂组件... Toyota Camry - 颜色: 白色, 发动机: 2.5L, 配件: 导航系统, 天窗 === 通过克隆创建新对象 === Toyota Camry - 颜色: 红色, 发动机: 2.5L, 配件: 导航系统, 天窗 Toyota Camry - 颜色: 蓝色, 发动机: 2.5L, 配件: 导航系统, 天窗, 真皮座椅 === 验证原型对象未被修改 === Toyota Camry - 颜色: 白色, 发动机: 2.5L, 配件: 导航系统, 天窗
Scénarios d'application avancés
Scénario 1 : Création de personnage dans le développement de jeux
Exemple
"""Prototype de personnage de jeu"""
def __init__(self, name: str, character_class: str, level: int = 1):
self.name = name
self.character_class = character_class
self.level = level
self.skills = []
self.equipment = {}
self.initialize_character()
def initialize_character(self):
"""Initialiser le personnage - simuler un chargement de données complexe"""
print(f"Chargement des données du personnage pour {self.name}...")
# Simuler le chargement des données depuis une base de données ou un fichier de configuration
base_skills = {
"Guerrier": ["Entaille", "Blocage", "Charge"],
"Mage": ["Boule de feu", "Flèche de glace", "Téléportation"],
"Archer": ["Tir précis", "Pose de pièges", "Déplacement rapide"]
}
self.skills = base_skills.get(self.character_class, [])
def add_skill(self, skill: str):
"""Ajouter une compétence"""
self.skills.append(skill)
def equip_item(self, slot: str, item: str):
"""Équiper l'objet"""
self.equipment[slot] = item
def clone(self) -> 'GameCharacter':
"""Cloner le personnage"""
return copy.deepcopy(self)
def show_status(self):
"""Afficher l'état du personnage"""
print(f"Personnage: {self.name} ({self.character_class}) - Niveau: {self.level}")
print(f"Compétences: {', '.join(self.skills)}")
if self.equipment:
equipment_str = ', '.join([f"{k}: {v}" for k, v in self.equipment.items()])
print(f"Équipement: {equipment_str}")
# Exemple d'utilisation
print("=== Exemple de prototype de personnage de jeu ===")
warrior_template = GameCharacter("Modèle de guerrier", "Guerrier")
warrior_template.equip_item("Arme", "Épée longue en acier")
warrior_template.equip_item("Armure", "Cotte de mailles")
warrior_template.show_status()
print("\n"=== Création des personnages joueurs ===")
player1 = warrior_template.clone()
player1.name = "Aventurier courageux"
player1.level = 5
player1.add_skill("Entaille tourbillonnante")
player1.show_status()
player2 = warrior_template.clone()
player2.name = "Gardien intrépide"
player2.level = 3
player2.equip_item("Bouclier", "Bouclier en acier")
player2.show_status()
Scénario 2 : Système de documents
Exemple
"""Prototype de modèle de document"""
def __init__(self, template_name: str):
self.template_name = template_name
self.headers = {}
self.content_sections = []
self.styles = {}
self.load_template_config()
def load_template_config(self):
"""Charger la configuration du modèle - simuler le chargement d'une configuration complexe"""
print(f"Chargement de la configuration du modèle pour {self.template_name}...")
# Simuler le chargement de la configuration depuis un fichier ou une base de données
self.headers = {
"title": f"{self.template_name} document",
"author": "Généré par le système",
"date": "2024-01-01"
}
self.styles = {
"font_family": "Arial",
"font_size": "12pt",
"line_spacing": "1.5"
}
def clone(self) -> 'DocumentTemplate':
"""Cloner le modèle de document"""
return copy.deepcopy(self)
def customize(self, title: str = None, author: str = None):
"""Personnaliser le document"""
if title:
self.headers["title"] = title
if author:
self.headers["author"] = author
self.headers["date"] = "2024-12-19" # Mettre à jour la date
def add_section(self, section_title: str, content: str):
"""Ajouter une section de contenu"""
self.content_sections.append({
"title": section_title,
"content": content
})
def render(self):
"""Afficher le document"""
print(f"\n=== {self.headers['title']} ===")
print(f"Auteur : {self.headers['author']}")
print(f"Date : {self.headers['date']}")
print(f"Styles : {self.styles}")
for section in self.content_sections:
print(f"\n## {section['title']}")
print(section['contenu'])
print("=" * 50)
# Exemple d'utilisation
print("=== Système de modèle de document ===")
report_template = DocumentTemplate("Rapport standard")
report_template.add_section("Introduction", "Ceci est la partie introduction du rapport.")
report_template.render()
print("\n"=== Création de rapports spécifiques ===")
monthly_report = report_template.clone()
monthly_report.customize("Rapport mensuel des ventes", "Service des ventes")
monthly_report.add_section("Données de ventes", "Les ventes de ce mois ont atteint 1 million de yuans.")
monthly_report.render()
project_report = report_template.clone()
project_report.customize("Rapport d'avancement du projet", "Chef de projet")
project_report.add_section("Progression du projet", "Le projet se déroule comme prévu.")
project_report.render()
Avantages et inconvénients du patron Prototype
Avantages
- Optimisation des performances: éviter d'exécuter de manière répétée des opérations d'initialisation coûteuses
- Simplifier le processus de création: Le client n'a pas besoin de connaître les détails de la création d'objets
- Configuration dynamique: Les produits peuvent être ajoutés ou retirés dynamiquement à l'exécution
- Réduction des sous-classes: Pas besoin de créer une sous-classe correspondante pour chaque produit
Inconvénients
- Complexité de la copie: Pour les objets complexes avec des références circulaires, la copie peut être complexe
- Utilisation de la mémoire: Si l'objet prototype est volumineux, la copie peut consommer beaucoup de mémoire
- Coût de la copie profonde: La copie profonde peut prendre plus de temps que la création d'une nouvelle instance
Bonnes pratiques et considérations
1. Choisir la méthode de copie appropriée
Exemple
def __init__(self, data):
self.data = data
self.reference_data = [] # Données qui peuvent avoir besoin d'être partagées
def clone(self):
"""Copie intelligente : choisir une copie superficielle ou profonde selon les besoins"""
new_obj = copy.copy(self) # Copier superficiellement l'objet principal
new_obj.reference_data = self.reference_data # Partager les données de référence
new_obj.data = copy.deepcopy(self.data) # Copier en profondeur les données importantes
return new_obj
2. Gérer les références circulaires
Exemple
def __init__(self, value):
self.value = value
self.children = []
def add_child(self, child):
self.children.append(child)
def clone(self):
"""Gérer les éventuelles références circulaires"""
# L'utilisation d'une copie profonde gère automatiquement les références circulaires
return copy.deepcopy(self)
3. Modèle de registre de prototypes
Exemple
"""Registre de prototypes - gérer plusieurs prototypes"""
def __init__(self):
self._prototypes = {}
def register_prototype(self, name: str, prototype: Prototype):
"""Enregistrer un prototype"""
self._prototypes[name] = prototype
def unregister_prototype(self, name: str):
"""Désenregistrer un prototype"""
if name in self._prototypes:
del self._prototypes[name]
def clone_prototype(self, name: str) -> Prototype:
"""Cloner un prototype par son nom"""
if name not in self._prototypes:
raise ValueError(f"Le prototype {name} n'est pas enregistré")
return self._prototypes[name].clone()
def list_prototypes(self):
"""Lister tous les prototypes disponibles"""
return list(self._prototypes.keys())
# Utilisation du registre
registry = PrototypeRegistry()
registry.register_prototype("basic_car", CarPrototype("Toyota", "Camry", "White", "2.5L"))
registry.register_prototype("warrior", GameCharacter("Warrior", "Warrior"))
# Créer rapidement des objets
new_car = registry.clone_prototype("basic_car")
new_warrior = registry.clone_prototype("warrior")
Exercices pratiques
À vous de jouer ! Essayez de compléter les exercices suivants pour consolider votre compréhension du patron Prototype :
Exercice 1 : Améliorer le prototype de voiture
ModifierCarPrototypeclasse, ajoutez les fonctionnalités suivantes :
- Enregistrer la date de fabrication de la voiture
- Ajouter la logique de génération du numéro d'identification du véhicule (VIN)
- Implémenter une méthode pour déterminer si deux objets voiture sont identiques
Exercice 2 : Créer un gestionnaire de configuration
Concevoir un prototype de gestionnaire de configuration avec les exigences suivantes :
- Capable de stocker les informations de configuration de l'application
- Prendre en charge le clonage et la personnalisation des configurations
- Fournir une fonctionnalité de validation de configuration
Exercice 3 : Implémenter la mise en cache de prototypes
Créer un système de prototypes avec mise en cache :
- Mettre en cache les objets prototypes couramment utilisés
- Fournir des mécanismes de nettoyage et de mise à jour du cache
- Prendre en charge la gestion des versions de prototypes
Résumé
Le patron Prototype est un patron de conception très pratique en Python. Il crée de nouveaux objets en copiant des objets existants et est particulièrement adapté aux scénarios suivants :
- Coût élevé de création d'objets: Lorsque le processus d'initialisation pour créer un nouvel objet est complexe ou chronophage
- Besoin d'objets similaires: Lorsque vous devez créer plusieurs objets similaires mais légèrement différents
- Configuration dynamique: Lorsque la configuration d'un objet peut changer à l'exécution
Points clés :
- Utiliser
copymodule pour implémenter la fonctionnalité de copie - Choisir une copie superficielle ou une copie profonde selon les besoins
- Envisager d'utiliser un registre de prototypes pour gérer plusieurs prototypes
- Veillez à gérer les références circulaires et les problèmes d'utilisation de la mémoire