Memento Pattern

Memento Pattern

vg

Das Memento Pattern ist ein Verhaltensmuster, das es ermöglicht, den Zustand eines Objekts zu speichern und zu einem späteren Zeitpunkt wiederherzustellen, ohne die interne Struktur des Objekts offenzulegen. Es besteht aus drei Hauptkomponenten: dem Originator, dem Memento und dem Caretaker. Das Memento Pattern hilft, den Zustand eines Objekts zu sichern und es später auf diesen Zustand zurückzusetzen, ohne dabei die Kapselung zu verletzen.

Was ist das Memento Pattern?

Das Memento Pattern wird verwendet, wenn der Zustand eines Objekts gespeichert und später wiederhergestellt werden muss. Es bietet eine Möglichkeit, Objekte zu „schnappschießen“ und zu einem früheren Zustand zurückzukehren. Dabei bleibt die Implementierung des Objekts privat, und der Zustand wird über ein Memento-Objekt gespeichert.

Komponenten des Memento Patterns

  1. Originator: Das Objekt, dessen Zustand gespeichert werden soll. Es erstellt ein Memento und stellt es zur Verfügung.
  2. Memento: Das Memento speichert den Zustand des Originators. Es enthält den Zustand, der später wiederhergestellt werden kann.
  3. Caretaker: Der Caretaker verwaltet das Memento. Er sorgt dafür, dass das Memento gespeichert und bei Bedarf abgerufen wird, ohne den Inhalt des Mementos zu kennen.

Beispiel des Memento Patterns in C++

Im folgenden Beispiel wird das Memento Pattern verwendet, um den Zustand eines einfachen Texteditors zu speichern. Der Editor kann Änderungen am Text vornehmen, und der Zustand des Textes kann zu einem späteren Zeitpunkt wiederhergestellt werden.

#include <iostream>
#include <string>
#include <memory>
#include <vector>

// Memento: Das Memento speichert den Zustand des Originators
class Memento {
private:
    std::string state;

public:
    Memento(const std::string& state) : state(state) {}

    std::string getState() const {
        return state;
    }
};

// Originator: Das Objekt, dessen Zustand gespeichert wird
class Originator {
private:
    std::string state;

public:
    void setState(const std::string& state) {
        this->state = state;
    }

    std::string getState() const {
        return state;
    }

    std::shared_ptr<Memento> createMemento() {
        return std::make_shared<Memento>(state);
    }

    void restoreMemento(const std::shared_ptr<Memento>& memento) {
        state = memento->getState();
    }
};

// Caretaker: Verwalter des Memento
class Caretaker {
private:
    std::vector<std::shared_ptr<Memento>> mementos;

public:
    void addMemento(const std::shared_ptr<Memento>& memento) {
        mementos.push_back(memento);
    }

    std::shared_ptr<Memento> getMemento(int index) {
        return mementos[index];
    }
};

// Client-Code
int main() {
    // Originator und Caretaker initialisieren
    Originator editor;
    Caretaker caretaker;

    // Ursprünglichen Zustand setzen
    editor.setState("Version 1: Hallo Welt");
    caretaker.addMemento(editor.createMemento());

    // Zustand ändern
    editor.setState("Version 2: Hallo C++");
    caretaker.addMemento(editor.createMemento());

    // Zustand wiederherstellen
    editor.restoreMemento(caretaker.getMemento(0));

    // Ausgabe des wiederhergestellten Zustands
    std::cout << "Wiederhergestellter Zustand: " << editor.getState() << std::endl;

    return 0;
}

Erklärung des C++-Beispiels

  1. Memento: Das Memento speichert den Zustand des Originator in einer privaten Variablen (state). Es stellt eine Methode getState() zur Verfügung, um den gespeicherten Zustand abzurufen.
  2. Originator: Der Originator hat ein state-Attribut, das den Zustand des Texteditors repräsentiert. Die Methode createMemento() erstellt ein Memento, das den aktuellen Zustand speichert. Die Methode restoreMemento() stellt den gespeicherten Zustand aus einem Memento wieder her.
  3. Caretaker: Der Caretaker verwaltet eine Liste von Mementos. Er fügt Mementos hinzu und kann bei Bedarf ein Memento aus der Liste abrufen. Der Caretaker kennt jedoch nicht den Inhalt des Mementos.
  4. Client: Der Client erstellt den Originator und den Caretaker. Er setzt den Zustand des Originators, speichert diesen Zustand in einem Memento und kann später zu einem gespeicherten Zustand zurückkehren.

Beispiel des Memento Patterns in Python

Das Memento Pattern ist ein Verhaltensmuster, das es ermöglicht, den Zustand eines Objekts zu speichern und später wiederherzustellen, ohne dass Details über die interne Implementierung des Objekts offengelegt werden.

Hier ist ein einfaches Beispiel, wie das Memento Pattern in Python implementiert werden kann:

Beispiel:

class Memento:
    """
    Memento speichert den Zustand eines Objekts
    """
    def __init__(self, state):
        self.state = state

    def get_state(self):
        return self.state


class Originator:
    """
    Originator erstellt ein Memento mit seinem aktuellen Zustand und kann es wiederherstellen
    """
    def __init__(self, state):
        self.state = state

    def create_memento(self):
        return Memento(self.state)

    def restore_memento(self, memento):
        self.state = memento.get_state()

    def get_state(self):
        return self.state

    def set_state(self, state):
        self.state = state


class Caretaker:
    """
    Caretaker verwaltet die Mementos und stellt sicher, dass die Historie gespeichert wird
    """
    def __init__(self):
        self.mementos = []

    def add_memento(self, memento):
        self.mementos.append(memento)

    def get_memento(self, index):
        return self.mementos[index]


# Beispiel der Verwendung
originator = Originator("Erster Zustand")
caretaker = Caretaker()

# Ursprünglichen Zustand speichern
caretaker.add_memento(originator.create_memento())
print(f"Ursprünglicher Zustand: {originator.get_state()}")

# Zustand ändern
originator.set_state("Zweiter Zustand")
print(f"Neuer Zustand: {originator.get_state()}")

# Wiederherstellung des gespeicherten Zustands
originator.restore_memento(caretaker.get_memento(0))
print(f"Wiederhergestellter Zustand: {originator.get_state()}")

Erklärung:

  1. Memento: Diese Klasse speichert den Zustand eines Objekts. Sie stellt eine Möglichkeit dar, den Zustand zu sichern und später abzurufen.
  2. Originator: Diese Klasse hat den Zustand, den wir speichern möchten. Sie kann ein Memento erstellen (den aktuellen Zustand speichern) und den Zustand aus einem Memento wiederherstellen.
  3. Caretaker: Diese Klasse verwaltet die gespeicherten Mementos. Sie hat keine Ahnung vom inneren Zustand der Originator-Klasse, sondern speichert einfach nur die Mementos.

Ausgabe des Programms:

Ursprünglicher Zustand: Erster Zustand
Neuer Zustand: Zweiter Zustand
Wiederhergestellter Zustand: Erster Zustand

In diesem Beispiel speichern wir den Zustand der Originator-Instanz (in der Klasse Memento), ändern den Zustand und können dann den alten Zustand wiederherstellen. Das Memento Pattern sorgt dafür, dass der ursprüngliche Zustand des Objekts sicher gespeichert wird und ohne die Notwendigkeit für den direkten Zugriff auf den internen Zustand des Objekts wiederhergestellt werden kann.

Vorteile des Memento Pattern

  1. Kapselung: Der Zustand eines Objekts kann gespeichert und wiederhergestellt werden, ohne dass andere Objekte Zugriff auf die interne Struktur des Objekts benötigen.
  2. Flexibilität: Das Memento Pattern ermöglicht es, den Zustand zu einem beliebigen Zeitpunkt zu speichern und später darauf zuzugreifen.
  3. Sicherheit: Der Caretaker hat keinen Zugriff auf die Details des Mementos. Dies sorgt dafür, dass der Zustand sicher und unveränderlich bleibt.
  4. Vermeidung von Änderungen: Änderungen am Zustand eines Objekts können rückgängig gemacht werden, ohne dass die Logik des Objekts oder des Clients verändert werden muss.

Nachteile des Memento Pattern

  1. Speicherverbrauch: Wenn häufig Mementos erstellt und gespeichert werden, kann der Speicherverbrauch steigen. Jedes Memento benötigt Speicherplatz für den Zustand.
  2. Komplexität: Das Memento Pattern kann zusätzliche Komplexität einführen, insbesondere wenn viele verschiedene Zustände gespeichert werden müssen.
  3. Verwaltung der Mementos: Der Caretaker muss die Mementos verwalten, was in großen Systemen unübersichtlich werden kann. Es erfordert eine klare Struktur und Organisation.

Wann sollte das Memento Pattern eingesetzt werden und wann nicht?

Das Memento Pattern ist besonders nützlich, wenn du den Zustand eines Objekts speichern und später wiederherstellen musst, ohne die Details des internen Zustands des Objekts preiszugeben. Hier sind einige Szenarien, in denen das Memento Pattern sinnvoll eingesetzt werden kann:

1. Undo/Redo-Operationen

  • Anwendungsfall: In Anwendungen, die Rückgängig- oder Wiederholen-Funktionen benötigen, z. B. Texteditoren, Grafiksoftware oder Spiele.
  • Beispiel: In einem Texteditor möchtest du sicherstellen, dass der Benutzer den vorherigen Zustand eines Dokuments wiederherstellen kann, nachdem eine Änderung vorgenommen wurde. Hier speichert das Memento Pattern den Zustand des Dokuments zu einem bestimmten Zeitpunkt, damit der Benutzer nachträglich darauf zurückgreifen kann.

2. Speichern von Konfigurationen

  • Anwendungsfall: Wenn du eine Softwareanwendung hast, deren Einstellungen zur Laufzeit geändert werden können, und du es den Benutzern ermöglichen möchtest, zwischen verschiedenen Konfigurationen zu wechseln.
  • Beispiel: Ein Benutzer ändert mehrere Einstellungen in einer Anwendung. Das Memento Pattern könnte verwendet werden, um den Zustand der Anwendung zu speichern, sodass der Benutzer zu einer früheren Konfiguration zurückkehren kann.

3. Spiele mit Zustandsänderungen

  • Anwendungsfall: In einem Spiel, in dem der Spieler den Fortschritt speichert, um später fortzufahren (z. B. Speicherstände in einem Adventure-Spiel).
  • Beispiel: Wenn ein Spieler in einem Spiel eine Reihe von Aktionen durchführt (z. B. einen Charakter verbessert oder ein Level abschließt), speichert das Memento Pattern den gesamten Zustand des Spiels, sodass der Spieler später genau an der gleichen Stelle weitermachen kann.

4. Verlauf von Änderungen

  • Anwendungsfall: Wenn du einen Verlauf von Änderungen verfolgen möchtest, ohne die inneren Details des Objekts preiszugeben.
  • Beispiel: In einer grafischen Anwendung, in der du z. B. mehrere Zeichenoperationen auf einer Leinwand durchführst, könntest du jede Änderung des Zustands in einem Memento speichern, um später zu einem bestimmten Punkt im Verlauf zurückzukehren.

5. Komplexe Objekte mit vielen Zuständen

  • Anwendungsfall: Wenn ein Objekt sehr viele interne Zustände hat, die durch verschiedene Benutzerinteraktionen oder Prozesse verändert werden können.
  • Beispiel: In einer Bankanwendung könnte ein Konto viele verschiedene Zustände haben, die während einer Sitzung geändert werden. Das Memento Pattern ermöglicht es, den Zustand des Kontos zu speichern, bevor Änderungen vorgenommen werden, sodass die Änderungen rückgängig gemacht werden können.

Vorteile des Memento Patterns:

  • Trennung von Verantwortlichkeiten: Der Originator (das Objekt mit dem Zustand) und der Caretaker (der Speicher des Zustands) sind voneinander getrennt. Der Caretaker muss keine Details über die interne Struktur des Originators wissen.
  • Verhindert die Offenlegung interner Details: Der interne Zustand des Objekts bleibt privat, was zu einer besseren Kapselung führt.
  • Ermöglicht einfaches Undo/Redo: Es ist einfach, den Zustand zu einem früheren Punkt zurückzusetzen, ohne die interne Implementierung des Objekts zu verändern.

Wann man es nicht verwenden sollte:

  • Hoher Speicherbedarf: Das Memento Pattern kann zu einem hohen Speicheraufwand führen, wenn zu viele Zustände gespeichert werden müssen (z. B. in einem sehr komplexen System mit vielen Änderungen).
  • Komplexität bei der Verwaltung von Mementos: Wenn es zu viele Zustände gibt, die verwaltet werden müssen, könnte der Code zur Verwaltung der Mementos selbst unübersichtlich und schwer wartbar werden.
  • Wenn der Zustand sehr flüchtig ist: Wenn der Zustand eines Objekts nur für kurze Zeit benötigt wird oder sehr häufig wechselt, könnte das Memento Pattern überflüssig sein, da der Aufwand für das Speichern und Wiederherstellen des Zustands den Nutzen übersteigt.

Das Memento Pattern eignet sich besonders für Szenarien, in denen Zustände von Objekten gespeichert und später wiederhergestellt werden müssen, ohne dass die interne Struktur des Objekts offengelegt werden muss – jedoch sollte es mit Bedacht eingesetzt werden, um zu vermeiden, dass unnötig viele Mementos gespeichert werden.

Fazit

Das Memento Pattern ist besonders nützlich, wenn der Zustand eines Objekts über längere Zeit gespeichert und später wiederhergestellt werden muss. Es schützt die Kapselung des Objekts, indem der Zustand in einem Memento-Objekt gespeichert wird. Das Beispiel in C++ zeigt, wie das Muster einfach implementiert werden kann und wie es eine flexible Möglichkeit bietet, den Zustand von Objekten zu verwalten. Das Memento Pattern eignet sich hervorragend in Situationen, in denen Objekte oft in verschiedene Zustände wechseln und diese Zustände später wiederhergestellt werden müssen.

Zurück zur Pattern-Liste: Liste der Design-Pattern

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)