We've talked about an undo support implementation in the previous section on the Command Pattern, and found it was not easy to implement the mechanism purely based on reversing all the operations. However, if we take snapshots of objects as their history, we may manage to avoid accumulating mistakes and make the system more stable. But then we have a problem: we need to store the states of objects while the states are encapsulated with objects themselves.
Memento Pattern helps in this situation. While a memento carries the state of an object at a certain time point, it also controls the process of setting the state back to an object. This makes the internal state implementation hidden from the undo mechanism in the following example:
We have the instances of the memento controlling the state restoration in the preceding structure. It can also be controlled by the caretaker, namely the undo mechanism, for simple state restoring cases.
The participants of Memento Pattern include:
restore
or other APIs for restoring the states to specific objectsMemento Pattern mainly does two things: it prevents the caretaker from knowing the internal state implementation and decouples the state retrieving and restoring process from states managed by the Caretaker
or Originator.
When the state retrieving and restoring processes are simple, having separated mementos does not help much if you are already keeping the decoupling idea in mind.
Start with an empty State
interface and Memento
class. As we do not want Caretaker
to know the details about state inside an Originator
or Memento
, we would like to make state
property of Memento
private. Having restoration logic inside Memento
does also help with this, and thus we need method restore
. So that we don't need to expose a public interface for reading state inside a memento.
And as an object assignment in JavaScript assigns only its reference, we would like to do a quick copy for the states (assuming state objects are single-level):
interface State { } class Memento { private state: State; constructor(state: State) { this.state = Object.assign({} as State, state); } restore(state: State): void { Object.assign(state, this.state); } }
For Originator
we use a getter and a setter for creating and restoring specific mementos:
class Originator { state: State; get memento(): Memento { return new Memento(this.state); } set memento(memento: Memento) { memento.restore(this.state); } }
Now the Caretaker
would manage the history accumulated with mementos:
class Caretaker { originator: Originator; history: Memento[] = []; save(): void { this.history.push(this.originator.memento); } restore(): void { this.originator.memento = this.history.shift(); } }
In some implementations of Memento Pattern, a getState
method is provided for instances of Originator
to read state from a memento. But to prevent classes other than Originator
from accessing the state
property, it may rely on language features like a friend modifier to restrict the access (which is not yet available in TypeScript).
Memento Pattern makes it easier for a caretaker to manage the states of originators and it becomes possible to extend state retrieving and restoring. However, a perfect implementation that seals everything might rely on language features as we've mentioned before. Using mementos could also bring a performance cost as they usually contain redundant information in trade of stability.
13.59.48.161