Patron de méthode modèle Python
Le patron Méthode Modèle est un patron de conception comportemental qui définit le squelette d'un algorithme dans une opération, en reportant certaines étapes aux sous-classes. Il permet aux sous-classes de redéfinir certaines étapes d'un algorithme sans changer la structure de l'algorithme.
En termes simples, c'est comme une recette de cuisine : la recette spécifie l'ordre des étapes (laver les légumes, les couper, les faire sauter, le dressage), mais la façon exacte de laver, couper et faire sauter peut être mise en œuvre par différents chefs selon leur propre style.
Pourquoi avons-nous besoin du patron Méthode Modèle ?
Réutilisation du code
Le patron Méthode Modèle déplace le comportement invariant dans la superclasse, évitant la duplication de code dans les sous-classes. Plusieurs sous-classes peuvent partager la structure d'algorithme définie par la même méthode modèle.
Extensibilité
Les sous-classes peuvent étendre ou modifier une partie du comportement de l'algorithme en redéfinissant des méthodes hook ou des méthodes d'étape concrètes, sans changer la structure globale de l'algorithme.
Inversion de contrôle
La classe parente contrôle le flux d'exécution de l'algorithme, et les sous-classes n'ont qu'à se concentrer sur les étapes spécifiques qu'elles doivent implémenter, ce qui réalise le « principe d'Hollywood » — « Ne nous appelez pas, nous vous appellerons. »
Structure du patron Méthode Modèle
Comprenons la structure du patron de méthode à travers un diagramme de classes :

Description des composants principaux
| Composant | Responsabilité | Description |
|---|---|---|
| AbstractClass | Définit le squelette de l'algorithme | Contient la méthode modèle et les méthodes d'étape abstraites |
| ConcreteClass | Implémente les étapes concrètes | Implémente les méthodes abstraites définies par la classe parente |
| template_method | Méthode modèle | Définit les parties invariantes de l'algorithme |
| step1, step2 | Méthode abstraite | Étapes spécifiques qui doivent être implémentées par les sous-classes |
| crochet | Méthode crochet | Étape facultative ; les sous-classes peuvent choisir de la redéfinir ou non |
Syntaxe de base et implémentation
Définition de la classe de base abstraite
Exemple
class AbstractClass(ABC):
"""Classe de base abstraite du patron de la méthode de gabarit"""
def template_method(self):
"""Méthode de gabarit - définit le squelette de l'algorithme"""
self.step1()
self.step2()
self.hook()
self.step3()
@abstractmethod
def step1(self):
"""Méthode abstraite 1 - doit être implémentée par les sous-classes"""
pass
@abstractmethod
def step2(self):
"""Méthode abstraite 2 - doit être implémentée par les sous-classes"""
pass
def step3(self):
"""Méthode concrète - a une implémentation par défaut"""
print("Exécuter l'étape 3 - implémentation par défaut")
def hook(self):
"""Méthode hook - étape facultative ; les sous-classes peuvent choisir de la redéfinir"""
print("Exécuter la méthode hook - par défaut ne fait rien")
Implémentation des sous-classes concrètes
Exemple
"""Classe d'implémentation concrète A"""
def step1(self):
print("ConcreteClassA - exécution de l'étape 1")
def step2(self):
print("ConcreteClassA - exécution de l'étape 2")
def hook(self):
print("ConcreteClassA - redéfinition de la méthode de hook pour ajouter des fonctionnalités supplémentaires")
class ConcreteClassB(AbstractClass):
"""Classe d'implémentation concrète B"""
def step1(self):
print("ConcreteClassB - exécution de l'étape 1")
def step2(self):
print("ConcreteClassB - exécution de l'étape 2")
# Ne pas redéfinir la méthode de hook ; utiliser l'implémentation par défaut
Exemples concrets d'application
Comprenons plus en profondeur le patron de méthode de modèle à travers plusieurs exemples pratiques.
Exemple 1 : Modèle de traitement de données
Exemple
import json
import csv
class DataProcessor(ABC):
"""Modèle de traitement de données"""
def process_data(self, input_file, output_file):
"""Méthode modèle de traitement de données"""
print(f"Début du traitement des données : {input_file} -> {output_file}")
# Lire les données
data = self.read_data(input_file)
print(f"Lecture de {len(data)} enregistrements")
# Transformer les données
transformed_data = self.transform_data(data)
print("Transformation des données terminée")
# Enregistrer les données
self.save_data(transformed_data, output_file)
print("Enregistrement des données terminé")
# Nettoyage
self.cleanup()
@abstractmethod
def read_data(self, file_path):
"""Lire les données - méthode abstraite"""
pass
@abstractmethod
def transform_data(self, data):
"""Transformer les données - méthode abstraite"""
pass
def save_data(self, data, file_path):
"""Enregistrer les données - méthode concrète"""
with open(file_path, 'w', encoding='utf-8') as f:
if isinstance(data, list):
for item in data:
f.write(str(item) + '\n')
else:
f.write(str(data))
def cleanup(self):
"""Nettoyage - méthode de hook"""
print("Nettoyage terminé")
class JSONProcessor(DataProcessor):
"""Processeur de données JSON"""
def read_data(self, file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
def transform_data(self, data):
# Simple transformation : convertir toutes les valeurs de chaîne en majuscules
if isinstance(data, dict):
return {k: v.upper() if isinstance(v, str) else v
for k, v in data.items()}
elif isinstance(data, list):
return [item.upper() if isinstance(item, str) else item
for item in data]
return data
class CSVProcessor(DataProcessor):
"""Processeur de données CSV"""
def read_data(self, file_path):
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def transform_data(self, data):
# Ajouter un horodatage de traitement à chaque enregistrement
import datetime
timestamp = datetime.datetime.now().isoformat()
for record in data:
record['processed_at'] = timestamp
return data
def cleanup(self):
"""Redéfinir la méthode cleanup pour ajouter une logique de nettoyage supplémentaire"""
print("Nettoyage du processeur CSV terminé")
print("Libérer les ressources de l'analyseur CSV")
# Exemple d'utilisation
if __name__ == "__main__":
# Créer des données de test
import os
# Test du processeur JSON
json_data = {'name': 'john', 'age': 30, 'city': 'new york'}
with open('test.json', 'w') as f:
json.dump(json_data, f)
json_processor = JSONProcessor()
json_processor.process_data('test.json', 'output_json.txt')
print("\n" + "="*50 + "\n")
# Tester le processeur CSV
csv_data = "nom,âge,ville\njohn,30,new york\njane,25,los angeles"
with open('test.csv', 'w') as f:
f.write(csv_data)
csv_processor = CSVProcessor()
csv_processor.process_data('test.csv', 'output_csv.txt')
# Nettoyer les fichiers de test
for file in ['test.json', 'test.csv', 'output_json.txt', 'output_csv.txt']:
if os.path.exists(file):
os.remove(file)
Exemple 2 : Modèle de préparation de boissons
Exemple
class BeverageMaker(ABC):
"""Modèle de préparation de boissons"""
def make_beverage(self):
"""Méthode modèle pour la préparation de boissons"""
self.boil_water()
self.brew()
self.pour_in_cup()
if self.customer_wants_condiments():
self.add_condiments()
self.serve()
def boil_water(self):
"""Faire bouillir l'eau - méthode concrète"""
print("Eau bouillante")
@abstractmethod
def brew(self):
"""Infuser - méthode abstraite"""
pass
def pour_in_cup(self):
"""Verser dans la tasse - méthode concrète"""
print("Verser dans la tasse")
@abstractmethod
def add_condiments(self):
"""Ajouter des condiments - méthode abstraite"""
pass
def customer_wants_condiments(self):
"""Méthode crochet - indique si le client souhaite des condiments"""
return True
def serve(self):
"""Servir la boisson - méthode concrète"""
print("Votre boisson est prête, dégustez !")
class CoffeeMaker(BeverageMaker):
"""Préparation du café"""
def brew(self):
print("Infuser le café moulu avec de l'eau bouillante")
def add_condiments(self):
print("Ajouter du sucre et du lait")
def customer_wants_condiments(self):
answer = input("Voulez-vous du sucre et du lait dans votre café ? (o/n) : ")
return answer.lower() == 'y'
class TeaMaker(BeverageMaker):
"""Préparation du thé"""
def brew(self):
print("Infuser les feuilles de thé dans de l'eau bouillante")
def add_condiments(self):
print("Ajouter du citron")
# Exemple d'utilisation
if __name__ == "__main__":
print("Préparation du café :")
coffee = CoffeeMaker()
coffee.make_beverage()
print("\n" + "="*30 + "\n")
print("Préparation du thé :")
tea = TeaMaker()
tea.make_beverage()
Variantes du patron de méthode
1. Patron de méthode avec paramètres
Exemple
"""Processeur de données configurable"""
def process_with_config(self, input_file, output_file, config):
"""Méthode modèle avec paramètres de configuration"""
self.validate_config(config)
data = self.read_data(input_file)
processed_data = self.process_with_config_impl(data, config)
self.save_data(processed_data, output_file)
self.post_process(config)
def validate_config(self, config):
"""Valider la configuration - méthode concrète"""
required_keys = ['format', 'encoding']
for key in required_keys:
if key not in config:
raise ValueError(f"Clé requise manquante dans la configuration : {key}")
@abstractmethod
def process_with_config_impl(self, data, config):
"""Implémentation pour le traitement des données à l'aide de la configuration"""
pass
def post_process(self, config):
"""Post-traitement - méthode hook"""
if config.get('cleanup', False):
print("Exécution des opérations de nettoyage")
2. Méthode modèle multi-étapes
Exemple
"""Processeur multi-étapes"""
def complex_processing(self):
"""Flux de traitement complexe"""
self.initialize()
self.pre_process()
self.main_process()
self.post_process()
self.finalize()
def initialize(self):
print("Initialisation du processeur")
def pre_process(self):
"""Prétraitement - méthode de raccordement"""
pass
@abstractmethod
def main_process(self):
"""Logique de traitement principale"""
pass
def post_process(self):
"""Post-traitement - méthode hook"""
pass
def finalize(self):
print("Traitement terminé")
Bonnes pratiques et considérations
1. Utiliser les méthodes abstraites de manière appropriée
- Ne déclarez comme méthodes abstraites que les méthodes qui doivent véritablement être implémentées par les sous-classes
- Fournissez des méthodes de raccordement avec des implémentations par défaut pour les étapes facultatives
2. Contrôle d'accès des méthodes de gabarit
- Les méthodes de modèle doivent généralement être déclarées comme
final(En Python, cela peut se faire via des conventions de nommage) - Les méthodes d'étape doivent être protégées pour éviter les appels externes directs
3. Gestion des erreurs
Exemple
"""Classe de modèle robuste"""
def template_method(self):
try:
self.setup()
self.execute_steps()
except Exception as e:
self.handle_error(e)
finally:
self.cleanup()
def execute_steps(self):
"""Séquence d'étapes d'exécution"""
self.step1()
self.step2()
self.step3()
def handle_error(self, error):
"""Gestion des erreurs - méthode hook"""
print(f"Une erreur s'est produite lors du traitement : {error}")
@abstractmethod
def setup(self):
pass
@abstractmethod
def step1(self):
pass
@abstractmethod
def step2(self):
pass
@abstractmethod
def step3(self):
pass
def cleanup(self):
print("Nettoyer les ressources")
Exercices pratiques
À vous de vous entraîner ! Réalisez les exercices suivants pour consolider votre compréhension du patron de méthode modèle :
Exercice 1 : Implémenter un modèle d'export de fichier
Créez un modèle d'export de fichier qui prend en charge l'exportation vers différents formats (TXT, HTML, JSON).
Exigences :
- Définir une classe de base abstraite
FileExporter - Implémenter des sous-classes concrètes
TXTExporter、HTMLExporter、JSONExporter - La méthode modèle doit inclure : la préparation des données, le formatage des données, la sauvegarde des fichiers, le post-traitement et d'autres étapes.
Exercice 2 : Améliorer le modèle de préparation de boissons
Étendre le modèle de préparation de boissons précédent et ajouter les fonctionnalités suivantes :
- Prise en charge de la sélection de la taille de la boisson (petite, moyenne, grande)
- Ajouter la fonctionnalité de calcul du prix
- Prise en charge des condiments personnalisés
Résumé
Le patron Template Method est un patron de conception puissant et pratique qui nous aide à construire un meilleur code des manières suivantes :
Principaux avantages
- Améliorer la réutilisabilité du code: Placer le code commun dans la classe parente
- Améliorer l'extensibilité: Étendre des étapes spécifiques via des sous-classes
- Respecter le principe Open/Closed: Ouvert à l'extension, fermé à la modification
- Inversion de contrôle: La classe parente contrôle le flux, et les sous-classes implémentent les détails
Scénarios applicables
- Plusieurs classes ont la même méthode, mais les implémentations concrètes diffèrent
- Besoin de contrôler les points d'extension des sous-classes
- Besoin de définir un squelette d'algorithme, mais permettre à certaines étapes de varier
Points clés
- La méthode Template définit les parties invariantes de l'algorithme
- Les méthodes abstraites définissent les parties variables de l'algorithme
- Les méthodes de raccordement fournissent des points d'extension facultatifs