Memento
- it lets you save and restore the previous state of an object without revealing the details of its implementation
Problem
- imagine developing a text editor app, it can do text editing, format text, and insert inline images
- you decided to let users undo any operations carried out on the text
- you decided to take the direct approach for implementation
- before performing any operation, the app records the state of all objects and saves it in some storage
- later when a user decides to revert an action, the app fetches the latest snapshot from the history and uses it to restore the state of all objects
- regarding the state snapshot, how would you produce one?
- you would need to go over all the fields in an object and copy their values into storage
- however, this would only work if the object had quite relaxed access restrictions to its contents
- unfortunately, most real objects won't let others peek inside them that easily, which hides all significant data in private fields
- assuming that our objects behave like hippies
- prefer open relations and keeping their state public
- this approach would solve the immediate problem and let you produce snapshots of objects' states at will
- it still has some serious issues
- in the future, you might decide to refactor some of the editor classes or add or remove some of the fields
- this would also require changing the classes responsible for copying the state of the affected objects
- Considering the actual snapshots of the editor's state
- what data does it contain
- at a bare minimum, it must contain the actual text, cursor coordinates, current scroll position, etc
- to make a snapshot, you would need to collect these values and put them into some kind of container
- most likely you would store lots of these container objects inside some list that would represent the history
- containers would end up being objects of 1 class
- class would have almost no methods, but lots of fields that mirror the editor's state
- to allow other objects to write and read data to and from a snapshot, the fields need to be public
- that would expose all the editor's state, private or not
- other classes would become dependent on every little change to the snapshot class
- what data does it contain
- this reaches a dead end
- you either expose all internal details of classes, making them too fragile
- or restrict access to their state, making it impossible to produce snapshots
Solution
- the problems are caused by broken encapsulation
- some objects try to do more than they are supposed to
- they try to invade the private space of other objects instead of letting these objects perform the actual action, so as to collect the data required to perform the action
- Memento pattern delegates creating the state snapshots to the actual owner of that state (the originator object)
- hence, instead of other objects trying to copy the editor's state from the outside
- the editor class itself can make the snapshot since it has full access to its own state
- hence, instead of other objects trying to copy the editor's state from the outside
- the pattern suggests storing the copy of the object's state in a special object called memento
- the contents of the memento aren't accessible to any other object except the one that produced it
- other objects must communicate with mementos using a limited interface which may allow fetching the snapshot's metadata
- but not the original object's state contained in the snapshot
- such a restrictive policy lets you store mementos inside other objects
- usually called caretaker
- since the caretaker works with the memento only via the limited interface
- it's not able to tamper with the state stored inside the memento
- at the same time, the originator has access to all fields inside the memento
- allowing it to restore its previous state at will
- in the text editor example, we can create a separate history class to act as the caretaker
- a stack of mementos stored inside the caretaker will grow each time the editor is about to execute an operation
- you could render this stack within the UI, displaying the history of previously performed operations to a user
- when a user triggers the undo, the history grabs the most recent memento from the stack and passes it back to the editor, requesting a roll-back
- since the editor has full access to the memento, it changes its own state with the values taken from the memento
Structure
The Originator class can produce snapshots of its own state, as well as restore its state from snapshots when needed.
The Memento is a value object that acts as a snapshot of the originator’s state.
- It’s a common practice to make the memento immutable and pass it the data only once, via the constructor.
The Caretaker knows not only “when” and “why” to capture the originator’s state, but also when the state should be restored.
- A caretaker can keep track of the originator’s history by storing a stack of mementos. When the originator has to travel back in history, the caretaker fetches the topmost memento from the stack and passes it to the originator’s restoration method.
In this implementation, the memento class is nested inside the originator.
- This lets the originator access the fields and methods of the memento, even though they’re declared private.
- On the other hand, the caretaker has very limited access to the memento’s fields and methods, which lets it store mementos in a stack but not tamper with their state.
When to use
- Use the Memento pattern when you want to produce snapshots of the object’s state to be able to restore a previous state of the object
- it lets you make full copies of an object’s state, including private fields, and store them separately from the object
- While most people remember this pattern thanks to the “undo” use case, it’s also indispensable when dealing with transactions
- Use the pattern when direct access to the object’s fields/getters/setters violates its encapsulation
- it makes the object itself responsible for creating a snapshot of its state
- No other object can read the snapshot, making the original object’s state data safe and secure
How to implement
Determine what class will play the role of the originator
- It’s important to know whether the program uses one central object of this type or multiple smaller ones
Create the memento class
- One by one, declare a set of fields that mirror the fields declared inside the originator class
Make the memento class immutable
- A memento should accept the data just once, via the constructor
- The class should have no setters
If your programming language supports nested classes, nest the memento inside the originator
- If not, extract a blank interface from the memento class and make all other objects use it to refer to the memento
- You may add some metadata operations to the interface, but nothing that exposes the originator’s state
Add a method for producing mementos to the originator class
- The originator should pass its state to the memento via one or multiple arguments of the memento’s constructor
- The return type of the method should be of the interface you extracted in the previous step (assuming that you extracted it at all)
- Under the hood, the memento-producing method should work directly with the memento class
Add a method for restoring the originator’s state to its class
- It should accept a memento object as an argument
- If you extracted an interface in the previous step, make it the type of the parameter
- In this case, you need to typecast the incoming object to the memento class, since the originator needs full access to that object
The caretaker, whether it represents a command object, a history, or something entirely different, should know when to request new mementos from the originator, how to store them and when to restore the originator with a particular memento
The link between caretakers and originators may be moved into the memento class
- In this case, each memento must be connected to the originator that had created it
- The restoration method would also move to the memento class
- However, this would all make sense only if the memento class is nested into originator or the originator class provides sufficient setters for overriding its state
Pros & Cons
Pros
- You can produce snapshots of the object’s state without violating its encapsulation
- You can simplify the originator’s code by letting the caretaker maintain the history of the originator’s state
Cons
- The app might consume lots of RAM if clients create mementos too often
- Caretakers should track the originator’s lifecycle to be able to destroy obsolete mementos
- Most dynamic programming languages, such as PHP, Python and JavaScript, can’t guarantee that the state within the memento stays untouched