Python-Memento-Muster (Memento)

Das Memento-Muster ist ein Verhaltens-Entwurfsmuster, das den internen Zustand eines Objekts erfassen und speichern kann, ohne die Kapselung zu verletzen, sodass es bei Bedarf in einen früheren Zustand wiederhergestellt werden kann.

Stellen Sie sich vor, Sie spielen ein Spiel, das eine "Speichern"-Funktion bietet. Sie können den aktuellen Spielfortschritt jederzeit speichern. Wenn Sie später scheitern oder ein Level erneut versuchen möchten, können Sie einen früheren Speicherstand laden. Das Memento-Muster ist eine Programmierlösung, die diese "Speichern-Laden"-Funktionalität implementiert.


Warum brauchen wir das Memento-Muster?

Anwendungsszenarien

  • Texteditor: Rückgängig- und Wiederholen-Funktionalität implementieren
  • Spieleentwicklung: Spielfortschritt und -zustand speichern
  • Grafiksoftware: Operationsverlauf unterstützen
  • Transaktionsverarbeitung: Bei fehlgeschlagener Operation auf einen früheren Zustand zurückrollen

Gelöste Probleme

  • Objektzustand speichern müssen, ohne interne Details offenzulegen
  • Einen Mechanismus zur Zustandswiederherstellung bereitstellen, der eine "Reue-Medizin"-Funktion (d. h. die Möglichkeit zum Rückgängigmachen) unterstützt
  • Die Objektkapselung erhalten und vermeiden, Logik zum Speichern von Zuständen mit Geschäftslogik zu vermischen

Kernkomponenten des Memento-Musters

Das Memento-Muster besteht aus drei Hauptrollen:

1. Originator

  • Das Objekt, das den Zustand speichern muss
  • Erstellt ein Memento, um den aktuellen Zustand aufzuzeichnen
  • Verwendet das Memento, um einen vorherigen Zustand wiederherzustellen

2. Memento

  • Speichert den internen Zustand des Originator-Objekts
  • Verhindert, dass andere Objekte als der Originator auf den Inhalt des Mementos zugreifen

3. Caretaker

  • Für das Speichern von Mementos verantwortlich
  • Kann den Inhalt von Mementos nicht bearbeiten oder einsehen


Python-Implementierung des Memento-Musters

Lass uns das Memento-Muster konkret anhand eines Texteditor-Beispiels implementieren.

Grundlegende Implementierung

Beispiel

class TextEditorMemento:
    """Memento-Klasse - speichert den Zustand des Texteditors"""
   
    def __init__(self, content):
        self._content = content
   
    def get_content(self):
        """Den gespeicherten Inhalt abrufen"""
        return self._content


class TextEditor:
    """Originator-Klasse - Texteditor"""
   
    def __init__(self):
        self._content = ""
   
    def write(self, text):
        """Text schreiben"""
        self._content += text
   
    def get_content(self):
        """Aktuellen Inhalt abrufen"""
        return self._content
   
    def save(self):
        """Memento erstellen - aktuellen Zustand speichern"""
        return TextEditorMemento(self._content)
   
    def restore(self, memento):
        """Zustand wiederherstellen - aus Memento lesen"""
        self._content = memento.get_content()


class HistoryManager:
    """Caretaker-Klasse - Verlaufsmanager"""
   
    def __init__(self):
        self._mementos = []
   
    def push(self, memento):
        """Memento speichern"""
        self._mementos.append(memento)
   
    def pop(self):
        """Neuestes Memento abrufen"""
        if self._mementos:
            return self._mementos.pop()
        return None

Vollständiger Beispielcode

Beispiel

# Vollständiges Beispiel für das Memento-Muster
def memo_pattern_demo():
    # Texteditor und Verlaufsmanager erstellen
    editor = TextEditor()
    history = HistoryManager()
   
    print("=== Texteditor Memento-Muster-Demo ==="\n")
   
    # Erste Bearbeitung und Speichern
    editor.write("Hallo, ")
    history.push(editor.save())
    print(f"Aktueller Inhalt: {editor.get_content()}")
    print("✅ Zustand gespeichert"\n")
   
    # Zweite Bearbeitung und Speichern
    editor.write("Welt!")
    history.push(editor.save())
    print(f"Aktueller Inhalt: {editor.get_content()}")
    print("✅ Zustand gespeichert"\n")
   
    # Dritte Bearbeitung, aber nicht gespeichert
    editor.write(" Dies ist neuer Text.")
    print(f"Aktueller Inhalt: {editor.get_content()}")
    print("❌ Aktuelle Bearbeitung nicht gespeichert"\n")
   
    # Rückgängig-Operation - zum zuletzt gespeicherten Zustand wiederherstellen
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"Inhalt nach Rückgängig: {editor.get_content()}")
        print("↩️ Zum vorherigen Speicherpunkt rückgängig gemacht\n")
   
    # Erneut rückgängig machen
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"Inhalt nach erneutem Rückgängigmachen: {editor.get_content()}")
        print("↩️ Zum Anfangszustand rückgängig gemacht")


# Demo ausführen
if __name__ == "__main__":
    memo_pattern_demo()

Ausführungsergebnis:


Fortgeschrittene Anwendung: Memento zur Unterstützung mehrerer Zustände

In echten Anwendungen müssen wir möglicherweise mehrere Attribute eines Objekts speichern. Hier ist ein komplexeres Beispiel:

Beispiel

import datetime
import json

class GameStateMemento:
    """Spielzustand-Memento"""
   
    def __init__(self, level, score, player_health, inventory):
        self._state = {
            'level': level,
            'score': score,
            'player_health': player_health,
            'inventory': inventory.copy(),  # Erstelle eine Kopie, um Referenzprobleme zu vermeiden
            'timestamp': datetime.datetime.now().isoformat()
        }
   
    def get_state(self):
        """Vollständigen Zustand abrufen"""
        return self._state.copy()
   
    def get_timestamp(self):
        """Speicherzeit abrufen"""
        return self._state['timestamp']


class Game:
    """Game-Klasse - 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):
        """Simuliert den Spielablauf"""
        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)
       
        # Gesundheit kann nicht negativ sein
        self.player_health = max(0, self.player_health)
   
    def display_status(self):
        """Aktuellen Zustand anzeigen"""
        status = f"""
🎮 Spielzustand:
Level: {self.level}
Punktzahl: {self.score}
Gesundheit: {self.player_health}
Inventar: {', '.join(self.inventory)}
        """

        print(status)
   
    def save_game(self):
        """Spielzustand speichern"""
        return GameStateMemento(
            self.level,
            self.score,
            self.player_health,
            self.inventory
        )
   
    def load_game(self, memento):
        """Spielzustand laden"""
        state = memento.get_state()
        self.level = state['level']
        self.score = state['score']
        self.player_health = state['player_health']
        self.inventory = state['inventory']


class SaveManager:
    """Spielstandverwalter"""
   
    def __init__(self):
        self.saves = {}
   
    def create_save(self, save_name, memento):
        """Spielstand erstellen"""
        self.saves[save_name] = memento
        print(f"💾 Spielstand '{save_name}' erfolgreich erstellt!")
   
    def load_save(self, save_name):
        """Spielstand laden"""
        if save_name in self.saves:
            print(f"🔄 Lade Spielstand '{save_name}'...")
            return self.saves[save_name]
        else:
            print(f"❌ Spielstand '{save_name}' existiert nicht!")
            return None
   
    def list_saves(self):
        """Alle Spielstände auflisten"""
        if not self.saves:
            print("📁 Keine Spielstände verfügbar")
            return
       
        print("\n📋 Spielstandliste:")
        for name, memento in self.saves.items():
            timestamp = memento.get_timestamp()
            print(f" - {name} (Gespeichert am: {timestamp})")


# Erweitertes Beispiel-Demo
def advanced_memo_demo():
    print("=== Spielstand-System-Demo ==="\n")
   
    game = Game()
    save_manager = SaveManager()
   
    # Anfangszustand
    print("🎯 Ein neues Spiel wird gestartet:")
    game.display_status()
   
    # Spielfortschritt
    print("🚀 Das Spiel wird gespielt...")
    game.play(level_increase=2, score_increase=500, new_item='shield')
    game.display_status()
   
    # Ersten Spielstand speichern
    save_manager.create_save("Level 1 abgeschlossen", game.save_game())
   
    # Das Spiel fortsetzen
    print("⚔️ Abenteuer fortsetzen...")
    game.play(level_increase=1, score_increase=200, health_change=-30, new_item='magic_wand')
    game.display_status()
   
    # Speichere den zweiten Spielstand
    save_manager.create_save("Level 2 startet", game.save_game())
   
    # Spielstandsliste anzeigen
    save_manager.list_saves()
   
    # Lade den ersten Spielstand
    print("\n⏪ Zurück zum abgeschlossenen Status von Level 1:")
    first_save = save_manager.load_save("Level 1 abgeschlossen")
    if first_save:
        game.load_game(first_save)
        game.display_status()


# Führe das fortgeschrittene Beispiel aus
if __name__ == "__main__":
    advanced_memo_demo()

Vor- und Nachteile des Memento-Musters

Vorteile

  1. Kapselungsschutz: Der interne Zustand eines Objekts wird nicht offengelegt, wodurch die Kapselung erhalten bleibt
  2. Vereinfachung des Originators: Trennt die Zustandsspeicherungslogik von der Geschäftslogik
  3. Einfach zu implementierende Wiederherstellung: Bietet einen einfachen Mechanismus zur Zustandswiederherstellung
  4. Unterstützung mehrerer Rückgängig-Schritte: Kann Zustände zu mehreren Zeitpunkten speichern

Nachteile

  1. Speicherverbrauch: Wenn der Zustand groß ist oder häufig gespeichert wird, wird viel Speicher verbraucht
  2. Caretaker-Overhead: Erfordert eine zusätzliche Klasse zur Verwaltung von Mementos
  3. Lebenszyklusverwaltung: Erstellung und Zerstörung von Mementos müssen ordnungsgemäß behandelt werden

Best Practices und Vorsichtsmaßnahmen

1. Speicheroptimierungsstrategie

Beispiel

# Anzahl der gespeicherten Verlaufseinträge begrenzen
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:
            # Entferne den ältesten Eintrag
            self._mementos.pop(0)
        self._mementos.append(memento)

2. Umgang mit komplexen Objektzuständen

Stellen Sie bei Objekten mit komplexen Referenzen sicher, dass das Memento eine tiefe Kopie erstellt:

Beispiel

import copy

class ComplexMemento:
    def __init__(self, complex_state):
        # Verwenden Sie eine tiefe Kopie, um Referenzprobleme zu vermeiden
        self._state = copy.deepcopy(complex_state)

3. Selektives Speichern

Nicht alle Zustände müssen gespeichert werden; Sie können auswählen, nur wichtige Eigenschaften zu speichern:

Beispiel

def create_selective_memento(self):
    """Wichtigen Zustand selektiv speichern"""
    important_state = {
        'essential_data': self.essential_data,
        'critical_settings': self.critical_settings
        # Temporäre Daten und Cache ignorieren
    }
    return SelectiveMemento(important_state)

Anwendungsszenarien aus der Praxis

1. Grafikeditor

Beispiel

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

2. Konfigurationsmanager

Beispiel

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

3. Transaktionsoperationen

Beispiel

class DatabaseTransaction:
    def create_checkpoint(self):
        return TransactionMemento(
            self.connection_state,
            self.pending_operations.copy()
        )

Zusammenfassung

Das Memento-Muster ist ein leistungsfähiges und praktisches Entwurfsmuster, das einen eleganten Mechanismus zum Speichern und Wiederherstellen von Zuständen bietet. Durch die Trennung der Zustandsspeicherungslogik von der Geschäftslogik bleibt der Code nicht nur sauber, sondern verbessert auch die Wartbarkeit des Systems.

Wichtige Punkte:

  • Der Kern des Memento-Musters ist der "Speichern-Wiederherstellen"-Mechanismus.
  • Die drei Kernrollen erfüllen jeweils ihre eigenen Aufgaben mit klaren Verantwortlichkeiten.
  • In der Praxis ist auf Speicherverwaltung und Zustandsauswahl zu achten.
  • Geeignet für Szenarien, die Rückgängigmachen, Wiederholen oder Zustands-Rollback erfordern.
Andere Erweiterungen