Many applications need Undo/Redo functionality. Commonly used implementation patterns are:
- Command Pattern
- Memento Pattern (state snapshots)
- State diffs
When using the Command Pattern one would encapsulate both the change logic and its reversal in command objects. Undo/Redo is implemented by managing stacks of those objects. This approach has its limitations, for example for changes that are unidirectional in nature, like anything involving randomness, encryption, etc.
State snapshots save the full state of the edited data as object graphs or some representation thereof. This is also called the Memento Pattern. It often uses serialization and typically compression of the object graph to reduce memory use and ensure immutable snapshots that can also be stored out-of-process, if desired.
State diffs are based on the idea of State snapshots, but only store the difference between states. This can vastly reduce memory consumption of your Undo/Redo history. It is based on diffing algorithms that compute the delta between two states (or their memento) and allow Undo/Redo by applying the deltas as patches against a given state. A disadvantage is that jumping to a state involves a whole chain of patch applications. But it is a good approach when the user mainly navigates the Undo/Redo history sequentially.
A highly reusable implementation of Undo/Redo using State Diffs is available at my github account: https://github.com/odoepner/diffing-history
It uses the following Open Source libraries:
- Protostuff for object graph serialization using runtime schema
- JavaxDelta for binary diffing and patching
It provides the following features:
- Unlimited Undo and Redo
- Can handle any type of Java objects
- Low memory footprint
- Straightforward type-safe API
- Supports stack size listeners
- Gzip compression for the serialized current state
It is Open Source under the Unlicense.
Usage
The main API is the History interface.
Create an instance of DiffingHistory to get started.
The DiffingHistoryTest calls all History methods and illustrates the API.
