Memento

In the section on the command pattern we talked briefly about the ability to undo operations. Creating reversible commands is not always possible. For many operations there is no apparent reversing operation which can restore the original state. For instance, imagine code which squares a number:

class SquareCommand {
  constructor(numberToSquare) {
    this.numberToSquare = numberToSquare;
  }
  Execute() {
    this.numberToSquare *= this.numberToSquare;
  }
}

Giving this code -9 will result in 81 but giving it 9 will also result in 81. There is no way to reverse this command without additional information.

The memento pattern provides an approach to restore the state of objects to a previous state. The memento keeps a record of the previous values of a variable and provides the functionality to restore them. Keeping a memento around for each command allows for easy restoration of non-reversible commands.

In addition to an undo-stack there are many instances where having the ability to roll back the state of an object is useful. For instance doing what-if analysis requires that you make some hypothetical changes to state and then observe how things change. The changes are generally not permanent so they could be rolled back using the memento pattern or, if the projects are desirable, left in place. A diagram of the memento pattern can be seen here:

Memento

A typical memento implementation involves three players:

  • Originator: The originator holds some form of state and provides an interface for generating new mementos.
  • Caretaker: This is the client of the pattern, it is what requests that new mementos be taken and governs when they are to be restored.
  • Memento: This is a representation of the saved state of the originator. This is what can be persisted to storage to allow for rolling back.

It can help to think of the members of the memento pattern as a boss and a secretary taking notes. The boss (caretaker) dictates some memo to the secretary (originator) who writes notes in a notepad (memento). From time to time the boss may request that the secretary cross out what he has just written.

The involvement of the caretaker can be varied slightly with the memento pattern. In some implementation the originator will generate a new memento each time a change is made to its state. This is commonly known as copy on write, as a new copy of the state is created and the change applied to it. The old version can be saved to a memento.

Implementation

In the land of Westeros there are a number of soothsayers, foretellers of the future. They work by using magic to peer into the future and examine how certain changes in the present will play out in the future. Often there is need for numerous foretelling with slightly different starting conditions. When setting their starting conditions, a memento pattern is invaluable.

We start off with a world state which gives information on the state of the world for a certain starting point:

class WorldState {
  constructor(numberOfKings, currentKingInKingsLanding, season) {
    this.numberOfKings = numberOfKings;
    this.currentKingInKingsLanding = currentKingInKingsLanding;
    this.season = season;
  }
}

This WorldState class is responsible for tracking all the conditions that make up the world. It is what is altered by the application every time a change to the starting conditions is made. Because this world state encompasses all the states for the application, it can be used as a memento. We can serialize this object and save it to disk or send it back to some history server somewhere.

The next thing we need is a class which provides the same state as the memento and allows for the creation and restoration of mementos. In our example we've called this as WorldStateProvider:

class WorldStateProvider {
  saveMemento() {
    return new WorldState(this.numberOfKings, this.currentKingInKingsLanding, this.season);
  }
  restoreMemento(memento) {
    this.numberOfKings = memento.numberOfKings;
    this.currentKingInKingsLanding = memento.currentKingInKingsLanding;
    this.season = memento.season;
  }
}

Finally we need a client for the foretelling, which we'll call Soothsayer:

class Soothsayer {
  constructor() {
    this.startingPoints = [];
    this.currentState = new WorldStateProvider();
  }
  setInitialConditions(numberOfKings, currentKingInKingsLanding, season) {
    this.currentState.numberOfKings = numberOfKings;
    this.currentState.currentKingInKingsLanding = currentKingInKingsLanding;
    this.currentState.season = season;
  }
  alterNumberOfKingsAndForetell(numberOfKings) {
    this.startingPoints.push(this.currentState.saveMemento());
    this.currentState.numberOfKings = numberOfKings;
  }
  alterSeasonAndForetell(season) {
    this.startingPoints.push(this.currentState.saveMemento());
    this.currentState.season = season;
  }
  alterCurrentKingInKingsLandingAndForetell(currentKingInKingsLanding) {
    this.startingPoints.push(this.currentState.saveMemento());
    this.currentState.currentKingInKingsLanding = currentKingInKingsLanding;
    //run some sort of prediction
  }
  tryADifferentChange() {
    this.currentState.restoreMemento(this.startingPoints.pop());
  }
}

This class provides a number of convenience methods which alter the state of the world and then run a foretelling. Each of these methods pushes the previous state into the history array, startingPoints. There is also a method, tryADifferentChange, which undoes the previous state change ready to run another foretelling. The undo is performed by loading back the memento which is stored in an array.

Despite a great pedigree it is very rare that client side JavaScript applications provide an undo function. I'm sure there are various reasons for this, but for the most part it is likely that people do not expect such functionality. However in most desktop applications, having an undo function is expected. I imagine that, as client side applications continue to grow in their capabilities, undo functionality will become more important. When it does, the memento pattern is a fantastic way of implementing the undo stack.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset