我建议使用不可变列表来解决撤销-重做问题。
不可变的数据结构是不会改变的。要将某些内容添加到不可变列表中,请在现有列表上调用 Add 方法并返回 一个新列表。因为新列表和旧列表都是不可变的,所以希望旧列表的大部分内存可以被新列表重用。
使用不可变的数据结构,撤消/重做很容易。您只需维护两个列表列表,列表的“撤消”列表和列表的“重做”列表。要撤消,您从撤消列表中取出第一个列表并将其放在重做列表中。要重做,你做相反的事情。这样您就不必担心撤消和重做所有这些突变;除了撤消和重做列表的值所引用的内容之外,没有任何变化。
有关 C# 中不可变数据结构的其他一些想法,请参阅我关于该主题的文章:
http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/
更新:
我不希望列表中的项目反映更改。我希望他们拥有我做突变之前的价值观
我不确定我是否理解该评论。让我勾勒出我的意思。
假设你有一个不可变的列表:
interface IImmutableList<T>
{
public IImmutableList<T> Append(T newItem);
public IImmutableList<T> RemoveLast();
public T LastItem { get; }
// and so on
}
sealed class ImList<T> : ImmutableList<T>
{
public static ImList<T> Empty = whatever;
// etc
}
好的,你想要一个当前列表,比如说,整数和一个撤消重做队列。
sealed class UndoRedo<T>
{
T current = default(T);
IImmutableList<T> undo = ImList<T>.Empty
IImmutableList<T> redo = ImList<T>.Empty;
public T Current
{
get { return current; }
set
{
undo = undo.Append(current);
redo = ImList<T>.Empty;
current = value;
}
}
public void Undo()
{
var newCurrent = undo.LastItem;
undo = undo.RemoveLast();
redo = redo.Append(current);
current = newCurrent;
}
public void Redo()
{
var newCurrent = redo.LastItem;
undo = undo.Append(current);
redo = redo.RemoveLast();
current = newCurrent;
}
}
现在你可以说
UndoRedo<IImmutableList<int>> undoredo = new UndoRedo<IImmutableList<int>>();
undoredo.SetCurrent(ImList<int>.Empty);
undoredo.SetCurrent(undoRedo.Current.Add(1));
undoredo.SetCurrent(undoRedo.Current.Add(2));
undoredo.SetCurrent(undoRedo.Current.Add(3));
undoredo.Undo();
undoredo.Undo();
undoredo.Redo();
undoredo.SetCurrent(undoRedo.Current.Add(4));
所以操作是这样的:
Start: undo: {} redo: {} curr: null
Set: undo: {null} redo: {} curr: {}
Add 1: undo: {null, {}} redo: {} curr: {1}
Add 2: undo: {null, {}, {1}} redo: {} curr: {1, 2}
Add 3: undo: {null, {}, {1}, {1, 2}} redo: {} curr: {1, 2, 3}
Undo: undo: {null, {}, {1}} redo: {{1, 2, 3}} curr: {1, 2}
Undo: undo: {null, {}} redo: {{1, 2, 3}, {1, 2}} curr: {1}
Redo: undo: {null, {}, {1}} redo: {{1, 2, 3}} curr: {1, 2}
Add 4: undo: {null, {}, {1, 2}} redo: {} curr: {1, 2, 4}
看,这个想法是因为每个列表都是不可变的,您在撤消和重做队列中维护当前的实际值,而不是拥有一个可变列表并且必须弄清楚如何将其变异回之前的状态。
诀窍在于提出一种可以重用其他数据结构内存的数据结构,因此存储 {null, {}, {1}, {1,2}} 实际上不会产生两个副本{1} 节点的。
一旦你有了不可变的数据,那么保持 整数列表 的 undo-redo 与 integers 或 strings 的 undo-redo 完全相同 或任何其他不可变数据类型。您只需存储状态,而不必担心有人会更改该状态。