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:
The original memento design pattern is implemented with the help of three main objects:
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.
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:
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.
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:
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.
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.
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.
3.144.47.218