I'll give yet another answer, but I think that the coverage has been insufficient so far.
The subject is far from trivial, and googling it returns a good number of results. Many applications implement a "undo" operation, and there are many variants.
There are 2 design patterns which can help us out here:
Command
: it's a reification of an action
Memento
: which consists in storing state (usually implies some form of serialization)
The Command
pattern is heavily used in graphic environments because there is usually various ways to accomplish an action. Think of save in Microsoft Word for example:
- you can click on the save icon
- you can go into File menu and click on Save
- you use the shortcut, typically CTRL+S
And of course save is probably implemented in term of save as.
The advantage of the Command
pattern here is twofold:
- you can create a stack of objects
- you can ask every object to implement an
undo
operation
Now, there are various issues proper to undo
:
- some operations cannot be undone (for example, consider
rm
on Linux or the empty trash bin action on Windows)
- some operations are difficult to undo, or it may not be natural (you need to store some state, the object is normally destroyed but here you would need to actually store it within the command for the undo action)
- generally we think of undo/redo as a stack, some software (graphics mainly) propose to undo items without actually undoing what has been done afterward, this is much more difficult to achieve, especially when the newer actions have been built on top of the to-undo one...
Because there are various problems, there are various strategies:
- For a simple Command, you might consider implementing an undo (for example, adding a character can be undone by removing it)
- For a more complex Command, you might consider implementing the undo as restoring the previous state (that's where
Memento
kick in)
- If you have lots of complex Command, that could mean lots of
Memento
s which consumes space, you can then use an approach which consists in only memorizing one Snapshot every 10 or 20 commands, and then redoing the commands from the latest snapshot up to the undone command
In fact, you can probably mix Command
and Memento
at leisure, depending on the specifics of your system and thus the complexity of either.
I would only considering undoing the last action executed to begin with (using a stack of action then). The functionality of undoing whatever action the user wishes is much more complicated.