Patron Memento Python (Memento)
Le patron Mémento est un patron de conception comportemental qui permet de capturer et d’enregistrer l’état interne d’un objet sans briser l’encapsulation, afin de pouvoir le restaurer dans un état précédent lorsque c’est nécessaire.
Imaginez que vous jouez à un jeu qui propose une fonctionnalité de « sauvegarde ». Vous pouvez sauvegarder la progression actuelle du jeu à tout moment. Si vous échouez plus tard ou souhaitez réessayer un niveau, vous pouvez charger une sauvegarde précédente. Le patron Mémento est une solution de programmation qui implémente cette fonctionnalité de « sauvegarde-chargement ».
Pourquoi avons-nous besoin du patron Mémento ?
Scénarios d'application
- Éditeur de texte: Implémenter les fonctionnalités d’annulation et de rétablissement
- Développement de jeux: Sauvegarder la progression et l’état du jeu
- Logiciel graphique: Prise en charge de l’historique des opérations
- Traitement des transactions: Revenir à un état précédent lorsqu’une opération échoue
Problèmes résolus
- Besoin d’enregistrer l’état d’un objet sans exposer les détails internes
- Fournir un mécanisme de récupération d’état, prenant en charge une fonction de « remède contre les regrets » (c’est-à-dire une capacité d’annulation)
- Maintenir l’encapsulation des objets et éviter de mélanger la logique de sauvegarde d’état avec la logique métier
Composants principaux du patron Mémento
Le patron Mémento se compose de trois rôles principaux :
1. Créateur
- L'objet qui doit sauvegarder son état
- Crée un mémento pour enregistrer l'état actuel
- Utilise le mémento pour restaurer un état précédent
2. Mémento
- Stocke l'état interne de l'objet créateur
- Empêche les objets autres que le créateur d'accéder au contenu du mémento
3. Gardien
- Responsable de la sauvegarde des mémentos
- Ne peut pas manipuler ni inspecter le contenu des mémentos

Implémentation en Python du patron Mémento
Implémentons le patron Memento concrètement à travers un exemple d’éditeur de texte.
Implémentation de base
Exemple
"""Classe Memento - sauvegarde l’état de l’éditeur de texte"""
def __init__(self, content):
self._content = content
def get_content(self):
"""Obtenir le contenu sauvegardé"""
return self._content
class TextEditor:
"""Classe Originator - Éditeur de texte"""
def __init__(self):
self._content = ""
def write(self, text):
"""Écrire du texte"""
self._content += text
def get_content(self):
"""Obtenir le contenu actuel"""
return self._content
def save(self):
"""Créer un mémento - enregistrer l'état actuel"""
return TextEditorMemento(self._content)
def restore(self, memento):
"""Restaurer l'état - lire depuis le mémento"""
self._content = memento.get_content()
class HistoryManager:
"""Classe gardienne - gestionnaire d'historique"""
def __init__(self):
self._mementos = []
def push(self, memento):
"""Enregistrer le mémento"""
self._mementos.append(memento)
def pop(self):
"""Obtenir le dernier mémento"""
if self._mementos:
return self._mementos.pop()
return None
Exemple de code complet
Exemple
def memo_pattern_demo():
# Créer un éditeur de texte et un gestionnaire d'historique
editor = TextEditor()
history = HistoryManager()
print("=== Démo du patron Mémento de l'éditeur de texte ==="\n")
# Première modification et sauvegarde
editor.write("Bonjour, ")
history.push(editor.save())
print(f"Contenu actuel : {editor.get_content()}")
print("✅ État sauvegardé\n")
# Deuxième modification et sauvegarde
editor.write("Monde !")
history.push(editor.save())
print(f"Contenu actuel : {editor.get_content()}")
print("✅ État sauvegardé\n")
# Troisième modification mais pas de sauvegarde
editor.write(" Ceci est un nouveau texte.")
print(f"Contenu actuel : {editor.get_content()}")
print("❌ Modification actuelle non sauvegardée\n")
# Opération d'annulation - restaurer au dernier état sauvegardé
memento = history.pop()
if memento:
editor.restore(memento)
print(f"Contenu après annulation : {editor.get_content()}")
print("↩️ Annulé jusqu'au point de sauvegarde précédent\n")
# Annuler à nouveau
memento = history.pop()
if memento:
editor.restore(memento)
print(f"Contenu après une nouvelle annulation : {editor.get_content()}")
print("↩️ Annulé jusqu'à l'état initial")
# Exécuter la démo
if __name__ == "__main__":
memo_pattern_demo()
Résultat de l'exécution:

Application avancée : Memento prenant en charge plusieurs états
Dans les applications réelles, nous pouvons avoir besoin de sauvegarder plusieurs attributs d'un objet. Voici un exemple plus complexe :
Exemple
import json
class GameStateMemento:
"""Mémento de l'état du jeu"""
def __init__(self, level, score, player_health, inventory):
self._state = {
'level': level,
'score': score,
'player_health': player_health,
'inventory': inventory.copy(), # Créer une copie pour éviter les problèmes de référence
'timestamp': datetime.datetime.now().isoformat()
}
def get_state(self):
"""Obtenir l'état complet"""
return self._state.copy()
def get_timestamp(self):
"""Obtenir l'heure de sauvegarde"""
return self._state['timestamp']
class Game:
"""Classe Game - Originator"""
def __init__(self):
self.level = 1
self.score = 0
self.player_health = 100
self.inventory = ['sword', 'potion']
def play(self, level_increase=1, score_increase=100, health_change=0, new_item=None):
"""Simuler le processus de jeu"""
self.level += level_increase
self.score += score_increase
self.player_health += health_change
if new_item and new_item not in self.inventory:
self.inventory.append(new_item)
# La santé ne peut pas être négative
self.player_health = max(0, self.player_health)
def display_status(self):
"""Affiche l'état actuel"""
status = f"""
🎮 État du jeu :
Niveau : {self.level}
Score : {self.score}
Santé : {self.player_health}
Inventaire : {', '.join(self.inventory)}
"""
print(status)
def save_game(self):
"""Sauvegarder l'état du jeu"""
return GameStateMemento(
self.level,
self.score,
self.player_health,
self.inventory
)
def load_game(self, memento):
"""Charger l'état du jeu"""
state = memento.get_state()
self.level = state['level']
self.score = state['score']
self.player_health = state['player_health']
self.inventory = state['inventory']
class SaveManager:
"""Gestionnaire de sauvegardes"""
def __init__(self):
self.saves = {}
def create_save(self, save, memento):
"""Créer une sauvegarde"""
self.saves[save_name] = memento
print(f"💾 Sauvegarde '{save_name}' créée avec succès !")
def load_save(self, save_name):
"""Charger une sauvegarde"""
if save_name in self.saves:
print(f"🔄 Chargement de la sauvegarde '{save_name}'...")
return self.saves[save_name]
else:
print(f"❌ La sauvegarde '{save_name}' n'existe pas !")
return None
def list_saves(self):
"""Lister toutes les sauvegardes"""
if not self.saves:
print("📁 Aucune sauvegarde disponible")
return
print("\n📋 Liste des sauvegardes :")
for name, memento in self.saves.items():
timestamp = memento.get_timestamp()
print(f" - {name} (Sauvegardé à : {timestamp})")
# Démo d'exemple avancé
def advanced_memo_demo():
print("=== Démo du système de sauvegarde de jeu ==="\n")
game = Game()
save_manager = SaveManager()
# État initial
print("🎯 Début d'une nouvelle partie :")
game.display_status()
# Progression du jeu
print("🚀 Partie en cours...")
game.play(level_increase=2, score_increase=500, new_item='bouclier')
game.display_status()
# Enregistrer la première sauvegarde
save_manager.create_save("Niveau 1 terminé", game.save_game())
# Continuer la partie
print("⚔️ Continuez l'aventure...")
game.play(level_increase=1, score_increase=200, health_change=-30, new_item='magic_wand')
game.display_status()
# Enregistrer la deuxième sauvegarde
save_manager.create_save("Le niveau 2 commence", game.save_game())
# Afficher la liste des sauvegardes
save_manager.list_saves()
# Charger la première sauvegarde
print("\n⏪ Retour à l'état terminé du niveau 1 :")
first_save = save_manager.load_save("Niveau 1 terminé")
if first_save:
game.load_game(first_save)
game.display_status()
# Exécuter l'exemple avancé
if __name__ == "__main__":
advanced_memo_demo()
Avantages et inconvénients du patron Mémento
Avantages
- Protection de l'encapsulation: N'expose pas l'état interne d'un objet, préservant ainsi l'encapsulation
- Simplification du créateur: Sépare la logique de sauvegarde d'état de la logique métier
- Récupération facile à implémenter: Fournit un mécanisme simple de restauration d'état
- Prise en charge de plusieurs annulations: Peut sauvegarder des états à plusieurs moments dans le temps
Inconvénients
- Consommation de mémoire: Si l'état est volumineux ou si les sauvegardes sont fréquentes, cela consomme beaucoup de mémoire
- Surcharge du gardien: Nécessite une classe supplémentaire pour gérer les mémentos
- Gestion du cycle de vie: Il faut gérer correctement la création et la destruction des mémentos
Bonnes pratiques et précautions
1. Stratégie d'optimisation de la mémoire
Exemple
class LimitedHistoryManager:
def __init__(self, max_size=10):
self._mementos = []
self._max_size = max_size
def push(self, memento):
if len(self._mementos) >= self._max_size:
# Supprimer l'enregistrement le plus ancien
self._mementos.pop(0)
self._mementos.append(memento)
2. Gestion des états d'objets complexes
Pour les objets avec des références complexes, assurez-vous que le memento crée une copie profonde :
Exemple
class ComplexMemento:
def __init__(self, complex_state):
# Utiliser une copie profonde pour éviter les problèmes de référence
self._state = copy.deepcopy(complex_state)
3. Sauvegarde sélective
Il n'est pas nécessaire de sauvegarder tous les états ; vous pouvez choisir de ne sauvegarder que les propriétés importantes :
Exemple
"""Sauvegarder sélectivement l'état important"""
important_state = {
'essential_data': self.essential_data,
'critical_settings': self.critical_settings
# Ignorer les données temporaires et le cache
}
return SelectiveMemento(important_state)
Scénarios d'application concrets
1. Éditeur graphique
Exemple
def save_state(self):
return GraphicMemento(
self.selected_shapes.copy(),
self.canvas_state,
self.view_settings
)
2. Gestionnaire de configuration
Exemple
def backup_config(self):
return ConfigMemento(
self.current_settings.copy(),
self.user_preferences
)
3. Opérations de transaction
Exemple
def create_checkpoint(self):
return TransactionMemento(
self.connection_state,
self.pending_operations.copy()
)
Résumé
Le patron Memento est un patron de conception puissant et pratique qui fournit un mécanisme élégant pour la sauvegarde et la restauration de l'état. En séparant la logique de sauvegarde d'état de la logique métier, il permet non seulement de garder le code propre, mais aussi d'améliorer la maintenabilité du système.
Points clés :
- Le cœur du patron Memento est le mécanisme de « sauvegarde-restauration ».
- Les trois rôles principaux remplissent chacun leurs propres fonctions avec des responsabilités claires.
- En pratique, faites attention à la gestion de la mémoire et au choix de l'état.
- Convient aux scénarios nécessitant une annulation, un rétablissement ou une restauration d'état.