Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the memento design pattern to save the state of multiple objects?

I'm very confused about how the memento is supposed to be implemented.

I understand the Memento has a State. And the Memento Pattern is used to store different (previous) states so that one can restore an object to previous states,

Well lets say I have multiple objects, each of this comes with 10 attributes, 5 of which stay the same throughout the life of each individual object, but 5 of which change. So i need, for each object to save it's previous states and go back to them.

The question:

How do I apply the Memento Pattern with these objects?

My idea so far:

So Memento Pattern has 3 classes, Memento of which you create many, one for each state. Caretaker which stores all the previous states of an object AKA Mementos. And then the Originator that creates Mementos and also gets the states from a Memento.

This would mean that each object instance would require it's own Caretaker instance (a list of it's previous states), and this Caretaker would have Mementos with the previous states of this object's 5 attributes (and also the current state or only previous ones?), but all objects with a caretaker can use the same Originator instance because the Originator can be used to create new mementos into any Caretaker.

Is this how it would be implemented or am I misunderstanding it?

It would look something like this:

Originator and Memento class

public class Memento {
   private Object1Attributes state;

   public Memento(Object1Attributes state){
      this.state = state;
   }

   public Object1Attributes getState(){
      return state;
   }    
}


static class Originator {
   private Object1Attributes state;

   public Memento saveStateToMemento(){
      return new Memento(state);
   }

   public void getStateFromMemento(Memento Memento){
      state = Memento.getState();
   }

   public void setState(Object1Attributes state){
      this.state = state;
   }

   public Object1Attributes getState(){
      return state;
   }

}

Other object

public class Object1Attributes{
    string attribute1;
    int attribute2;
    someObject attribute3;
    someObject attribute4;
    someObject attribute5;
}

public class Object1 {

    CareTaker careTaker = new CareTaker();

    Object1Attributes;

    string attribute6;
    int attribute7;
    someObject attribute8;
    someObject attribute9;
    someObject attribute10;


    public void returnToPreviousState(){
        if(caretaker.Length()>0){
            Object1Attributes = originator.getStateFromMemento(careTaker.get(caretaker.Length()-1));
            caretaker.remove(caretaker.Length()-1);
        }
   }

    public void newState(ObjectAttributes OA){
        originator.setState(OA);
        this.ObjectAttributes = OA;
        this.caretaker.add(originator.saveStateToMemento());

    }

}

Another option

would be making the Memento class and the Originator class hold the 5 attributes, instead of encapsulating the 5 attributes inside another class. Like so:

public class Originator {
    string attribute1;
    int attribute2;
    someObject attribute3;
    someObject attribute4;
    someObject attribute5;

   public Memento saveStateToMemento(){
      return new Memento(attribute1, attribute2, attribute3, attribute4, attribute5);
   }

   public void setAttribute1(string state){
      this.attribute1 = state;
   }

   public void setAttribute2(int state){
      this.attribute2 = state;
   }

}

This way each Object1 instance would hold its own instance of Originator instead of Object1Attributes though, and this Originator would contain the current state of the attributes in the object1 instance; I don't know which way is the correct way of implementing the pattern.

All examples online use mementos to store a "state" which is just a string, none of them involve the creation of multiple objects that can have multiple states, so that's why I'm so unsure.

like image 208
Jose V Avatar asked Nov 09 '22 05:11

Jose V


1 Answers

One of the common pitfalls when implementing the Memento pattern is exposing the Originator's internal state. This breaks one of the fundamental OO-concepts called encapsulation.

The Memento has a state which captures the Originator's state. This can be an external type. You can also declare the needed properties in the Memento itself. Later you can use the Mediator's state to revert back the Originator to that state.

class Memento {
    var state: State
}

The Originator shall have two Memento-related methods:

class Originator {
    func createMemento() -> Memento {
        let currentState = State(...)
        return Memento(state: currentState)
    }

    func apply(memento: Memento) {
        let restoreState = memento.state
        //...set the properties you want to restore from the memento state
    }
}

Finally, the Caretaker:

final class Caretaker {
    private lazy var mementos = [String: Memento]()

    func saveState(originator: Originator, identifier: String) {
        let snapshot: GameWorldMemento = originator.createMemento()
        snapshots[identifier] = snapshot
    }

    func restoreState(originator: Originator, identifier: String) {
        if let snapshot = snapshots[identifier] {
            originator.apply(memento: snapshot)
        }
    }
}

And this is how to use it:

let caretaker = CareTaker()
let originator = Originator()

// Save initial state
caretaker.saveState(originator: originator, identifier: "save0")

// modify the originator
// ...
// reset the originator to its original state
caretaker.restoreState(originator: originator, identifier: "save0")

This is just a simplified example to illustrate the concept.

Normally, I'd start by defining the three protocols. Since the concrete "originators" are usually already existing types, I'd add a type extension to make it adopt the Originator protocol. This way I don't have to modify its code. Actually, this way I can enhance types without modifying their original code.

Hope this helps.

like image 192
Karoly Nyisztor Avatar answered Dec 28 '22 07:12

Karoly Nyisztor