22. Januar 2013

Entwurfsmuster: Memento

Ein weiteres Entwurfsmuster, das in dieser Serie vorgestellt werden soll, ist das Memento-Pattern (teilweise auch "Token" genannt). Es gehört zur Familie der Verhaltensmuster und dient dazu den Zustand eines Objekts zu einem bestimmten Zeitpunkt zu speichern, so dass dieser später wiederhergestellt werden kann. Mithilfe des Patterns lässt sich die Anforderung umsetzen, ohne die Kapselung der objektinternen Daten zu verletzen. Vorstellbar ist eine solche Implementierung für die Umsetzung von Haltepunkten oder von Undo-Mechanismen. Im Folgenden ist das UML-Klassendiagramm des Memento-Patterns zu sehen:

Das UML-Diagramm des Memento-Patterns (Klicken zum Vergrößern)

Die Klasse "Originator" stellt die Klasse dar, von der ein Memento erstellt werden soll. Die Verantwortlichkeit hierfür liegt bei der Klasse selbst, da nur sie ihren internen Zustand kennt. Die Klasse "Memento" stellt das Memento dar, in dem der Objektzustand gespeichert werden kann. Dafür müssen die notwendigen Attribute, sowie jeweils Getter- und Setter-Methoden vorhanden sein. Für die Erstellung eines Mementos bietet die Klasse "Originator" eine Methode an. Auch für die spätere Wiederherrstellung ihres vorherigen Zustandes besitzt sie eine Methode, die ein Memento-Objekt entgegennimmt. Die Klasse "CareTaker" ist für die Zwischenspeicherung eines oder mehrerer Mementos zuständig, bis sie wieder gebraucht werden. Dabei darf sie die Integrität der Memento natürlich nicht durch deren Veränderung verletzen.

Vorteile:
  • Die interne Repräsentation des Objekts, dessen Zustand gespeichert werden soll, wird nicht nach außen hin offen gelegt.
Nachteile:
  • Abhängig von den Daten, die das Objekt beinhaltet, kann die Erstellung eines Memento-Objektes sehr aufwändig sein.
  • Es ist zusätzliche Logik nötig um die erstellten Mementos bis zu ihrer Wiederverwendung zwischenzuspeichern. 
Alternativen zu einem Memento stellen der Zugriff über einen Proxy, der die Sicherung und die spätere Wiederherstellung des Objektzustandes vornimmt, oder die Serialisierung bzw. Deserialisierung des Objekts dar.

Das folgende einfache Java-Beispiel soll die Verwendung des Memento-Patterns, anhand der Implementierung einer Undo-Funktionalität, verdeutlichen. Zunächst muss die Klasse definiert werden, deren Zustand gespeichert werden soll. In diesem Beispiel handelt es sich bei dem Zustand lediglich um einen einzigen String.
 public class Originator {  
   
   private String state = "";  
   
   public void setState(String state) {  
     this.state = state;  
   }  
   
   public String getState() {  
     return state;  
   }  
   
   public Memento createMemento() {  
     return new Memento(state);  
   }  
   
   public void setMemento(Memento memento) {  
     state = memento.getState();  
   }  
   
 }  
Das entsprechende Memento zu der Klasse sieht folgendermaßen aus:

 public class Memento {  
   
   private final String state;  
   
   public Memento(final String state) {  
     this.state = state;  
   }  
   
   public String getState() {  
     return state;  
   }  
   
 }  
Außerdem wird noch eine Klasse benötigt, die Mementos speichern und wieder zurückgeben kann. Die folgende Implementierung benutzt hierfür eine Liste und besitzt eine Methode zum Hinzufügen Mementos und eine zum Zurückgeben des jeweils zuletzt hinzugefügten Mementos.
 public class CareTaker {  
   
   private List<Memento> mementos = new ArrayList<>();  
   
   public void addMemento(Memento memento) {  
     mementos.add(memento);  
   }  
   
   public Memento getLastMemento() {  
     Memento result = mementos.get(mementos.size() - 1);  
     mementos.remove(result);  
     return result;  
   }  
   
 }  
Eine beispielhafte Verwendung all dieser Klassen könnte in etwa so aussehen:
 CareTaker careTaker = new CareTaker();  
 Originator = new Originator();  
 originator.setState("State1");  
 careTaker.addMemento(originator.createMemento());  
 originator.setState("State2");  
 careTaker.addMemento(originator.createMemento());  
 originator.setState("State3");  
   
 System.out.println(originator.getState());  
 originator.setMemento(careTaker.getLastMemento());  
 System.out.println(originator.getState());  
 originator.setMemento(careTaker.getLastMemento());  
 System.out.println(originator.getState());  
Insgesamt wird der Zustand drei mal geändert und jedes mal wird ein Memento erstellt und dem CareTaker übergeben. Anschließend werden alle Mementos nacheinander wieder zurückgespielt, bis der Ausgangszustand wieder erreicht ist. Das Programm liefert folgende Ausgabe: 
 State3  
 State2  
 State1  

Keine Kommentare:

Kommentar veröffentlichen