In my opinion, the UNDO/REDO could be implemented in 2 ways broadly.
1. Command Level (called command level Undo/Redo)
2. Document level (called global Undo/Redo)
Command level: As many answers point out, this is efficiently achieved using Memento pattern. If the command also supports journalizing the action, a redo is easily supported.
Limitation: Once the scope of the command is out, the undo/redo is impossible, which leads to document level(global) undo/redo
I guess your case would fit into the global undo/redo since it is suitable for a model which involves a lot of memory space. Also, this is suitable to selectively undo/redo also. There are two primitive types
- All memory undo/redo
- Object level Undo Redo
In "All memory Undo/Redo", the entire memory is treated as a connected data (such as a tree, or a list or a graph) and the memory is managed by the application rather than the OS. So new and delete operators if in C++ are overloaded to contain more specific structures to effectively implement operations such as a. If any node is modified, b. holding and clearing data etc.,
The way it functions is basically to copy the entire memory(assuming that memory allocation is already optimized and managed by the application using advanced algorithms) and store it in a stack. If the copy of the memory is requested, the tree structure is copied based on the need to have a shallow or deep copy. A deep copy is made only for that variable which is modified. Since every variable is allocated using custom allocation, the application has the final say when to delete it if need be.
Things become very interesting if we have to partition the Undo/Redo when it so happens that we need to programatically-selectively Undo/Redo a set of operation. In this case, only those new variables, or deleted variables or modified variables are given a flag so that Undo/Redo only undoes/redoes those memory
Things become even more interesting if we need to do a partial Undo/Redo inside an object. When such is the case, a newer idea of "Visitor pattern" is used. It is called "Object Level Undo/redo"
- Object level Undo/Redo: When the notification to undo/redo is called, every object implements a streaming operation wherein, the streamer gets from the object the old data/new data which is programmed. The data which is not be disturbed is left undisturbed. Every object gets a streamer as argument and inside the UNDo/Redo call, it streams/unstreams the data of the object.
Both 1 and 2 could have methods such as
1. BeforeUndo()
2. AfterUndo()
3. BeforeRedo()
4. AfterRedo(). These methods have to be published in the basic Undo/redo Command ( not the contextual command) so that all objects implement these methods too to get specific action.
A good strategy is to create a hybrid of 1 and 2. The beauty is that these methods(1&2) themselves use command patterns