The memento design pattern

Depending on the software we are writing, we might have a requirement to be able to restore the state of an object back to its previous state. The purpose of the memento design pattern is to:

Note

Provide the ability to execute an undo action in order to restore an object to a previous state.

The original memento design pattern is implemented with the help of three main objects:

  • Originator: The object whose state we want to be able to restore
  • Caretaker: The object that triggers the changes to the originator object and uses the memento objects for rollback, if needed
  • Memento: The object that carries the actual state of the originator and can be used to restore to one of the previous states

It is important to know that the memento object can be handled only by the originator. The caretaker and all other classes can just store it and nothing else.

Class diagram

A classical example of the memento design pattern that comes to mind is text editors. We can always undo whatever we have changed. We will present something similar in our class diagram and example.

The following is the class diagram:

Class diagram

As you can see in the preceding diagram, our caretaker is the TextEditorManipulator. It automatically saves the state in the states stack on every manipulation. The TextEditor implements the Originator and creates a memento object and restores from one. Finally, the TextEditorMemento is the concrete memento object that our text editor will be using to save the state. Our state is just the current string representation of the text in the editor.

Code example

In this subsection, we will go through the text editor code one step at a time and see how the memento design pattern could be implemented in Scala.

First of all, let's see the Caretaker, Memento, and Originator traits:

trait Memento[T] {
  protected val state: T
  def getState(): T = state
}

trait Caretaker[T] {
  val states: mutable.Stack[Memento[T]] = mutable.Stack[Memento[T]]()
}

trait Originator[T] {
  def createMemento: Memento[T]
  def restore(memento: Memento[T])
}

We have used generics, and this allows us to reuse those traits multiple times when we want to implement the memento design pattern. Let's now take a look at the specific implementations of the traits that are necessary in our application:

class TextEditor extends Originator[String] {
  private var builder: StringBuilder = new StringBuilder
  
  def append(text: String): Unit = {
    builder.append(text)
  }
  
  def delete(): Unit = {
    if (builder.nonEmpty) {
      builder.deleteCharAt(builder.length - 1)
    }
  }

  override def createMemento: Memento[String] = new TextEditorMemento(builder.toString)

  override def restore(memento: Memento[String]): Unit = this.builder = new StringBuilder(memento.getState())

  def text(): String = builder.toString
  
  private class TextEditorMemento(val state: String) extends Memento[String]
}

The preceding code shows the actual Originator implementation as well as the Memento one. It is common to create the memento class as private to the object, which will be creating and restoring from the class, and that's why we have done the same. The reason for this is that the originator should be the only one who knows how to create and restore from a memento object and how to read its state.

Finally, let's take a look at the Caretaker implementation:

class TextEditorManipulator extends Caretaker[String] {
  private val textEditor = new TextEditor
  
  def save(): Unit = {
    states.push(textEditor.createMemento)
  }
  
  def undo(): Unit = {
    if (states.nonEmpty) {
      textEditor.restore(states.pop())
    }
  }
  
  def append(text: String): Unit = {
    save()
    textEditor.append(text)
  }
  
  def delete(): Unit = {
    save()
    textEditor.delete()
  }
  
  def readText(): String = textEditor.text()
}

In our implementation, the caretaker exposes methods to manipulate the originator object. Before every manipulation, we save the state to the stack in order to be able to rollback if needed at a future point.

Now that we've seen all the code for our example, let's see an application that uses it:

object TextEditorExample {
  def main(args: Array[String]): Unit = {
    val textEditorManipulator = new TextEditorManipulator
    textEditorManipulator.append("This is a chapter about memento.")
    System.out.println(s"The text is: '${textEditorManipulator.readText()}'")
    // delete 2 characters
    System.out.println("Deleting 2 characters...")
    textEditorManipulator.delete()
    textEditorManipulator.delete()
    // see the text
    System.out.println(s"The text is: '${textEditorManipulator.readText()}'")
    // undo
    System.out.println("Undoing...")
    textEditorManipulator.undo()
    System.out.println(s"The text is: '${textEditorManipulator.readText()}'")
    // undo again
    System.out.println("Undoing...")
    textEditorManipulator.undo()
    System.out.println(s"The text is: '${textEditorManipulator.readText()}'")
  }
}

In the preceding code, we just manually added some text to our text editor, deleted some characters, and then did an undo of the deletions. The following screenshot shows the output of this example:

Code example

One possible issue with our application design that might need improvement is the states stack—we have absolutely no limit and if a lot of changes are made, it could grow too much. In real text editors, we cannot go back infinitely, and this stack is limited to a certain number of operations. Another performance issue could be the fact that we call toString on the internal StringBuilder on each operation. Passing the actual StringBuilder, however, could have undesired effects on the application, as changes will affect all of the builder's references.

What is it good for?

The memento design pattern is useful for applications that want to support a revertable state. In our example, we used a stack of states; however, this is not necessary—some applications might need only the last operation to be saved.

What is it not so good for?

Developers should be careful when they use the memento design pattern. They should try to have the state saved in value objects if possible because if a mutable type is passed, it would be changed by reference and this will lead to unwanted results. Developers should also be careful about how far back in time they allow changes to be undoable because the more operations are saved in the stack, the more memory will be required. Finally, Scala is immutable and the memento design pattern does not always coincide with the language philosophy.

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

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