The canonical strategy is to use the Command pattern. You'll represent the things you can do as Command objects, and each object is placed on a stack. The state of the application is then defined by an initial state plus everything that the stack has. Thus, the "undo" operation is then just popping the top stack item and reapplying the remaining items to the initial state.
In practice, sometimes it's expensive to keep applying those operations to the initial state to generate the current state. For example, this might be true with something like a complex series of image adjustments, like you might find in Photoshop. In cases like that, it's common to keep an alternating state-stack series in memory:
+---------+
| state_0 |
+---------+ +---------+
| next -------> | stack_0 |
+---------+ +---------+
| data | +---------+
| next -------> | state_1 |
+---------+ +---------+
| next ----- ...
+---------+
Each stack_i
holds commands until it exceeds some pre-set complexity (e.g., the commands exceed computational cost X
) or ordinal (e.g. the stack holds X
or more commands) limit. At that point, a new intermediate state object state_i+1
is created to encapsulate the state, and a new, empty stack stack_i+1
is created to hold new commands.
In this way, you only have to apply a small sequence of commands to the last snapshotted state to get the current state. This comes at the expense of memorizing entire states, which may not be feasible for very large applications, but you can choose to snapshot only a set of the state to optimize.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…