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

class TextEditorMemento:
    """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

# Exemple complet du patron Mémento
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 datetime
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

  1. Protection de l'encapsulation: N'expose pas l'état interne d'un objet, préservant ainsi l'encapsulation
  2. Simplification du créateur: Sépare la logique de sauvegarde d'état de la logique métier
  3. Récupération facile à implémenter: Fournit un mécanisme simple de restauration d'état
  4. Prise en charge de plusieurs annulations: Peut sauvegarder des états à plusieurs moments dans le temps

Inconvénients

  1. Consommation de mémoire: Si l'état est volumineux ou si les sauvegardes sont fréquentes, cela consomme beaucoup de mémoire
  2. Surcharge du gardien: Nécessite une classe supplémentaire pour gérer les mémentos
  3. 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

# Limiter le nombre d'enregistrements d'historique sauvegardés
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

import copy

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

def create_selective_memento(self):
    """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

class GraphicEditor:
    def save_state(self):
        return GraphicMemento(
            self.selected_shapes.copy(),
            self.canvas_state,
            self.view_settings
        )

2. Gestionnaire de configuration

Exemple

class ConfigManager:
    def backup_config(self):
        return ConfigMemento(
            self.current_settings.copy(),
            self.user_preferences
        )

3. Opérations de transaction

Exemple

class DatabaseTransaction:
    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.
Autres extensions