我需要为我的窗口应用程序(像powerpoint这样的编辑器)实现撤消/重做框架工作,应该遵循的最佳实践,如何处理我的对象的所有属性更改以及它在UI上的反射。
答案 0 :(得分:29)
有两种经典模式可供使用。第一个是memento pattern,用于存储完整对象状态的快照。这可能比命令模式更加系统密集,但它允许非常简单地回滚到较旧的快照。您可以将快照存储在磁盘上,然后将其存储在PaintShop / PhotoShop中,或者将它们保存在内存中,以用于不需要持久性的较小对象。你正在做的正是这个模式的设计,所以它应该比其他人建议的命令模式略好。
此外,另外需要注意的是,因为它不需要您使用相反的命令撤消先前完成的操作,这意味着任何可能无法撤消的可能单向函数[例如散列或加密]通过回滚到较旧的快照,可以简单地使用互惠命令撤消。
同样正如所指出的那样command pattern可能资源密集程度较低,所以我会在特定情况下承认:
命令模式可能更适合[但不一定,它将在很大程度上取决于情况]。在其他情况下,我会使用memento模式。
我可能会避免使用这两者的混搭,因为我倾向于关心开发人员,这些开发人员会支持我,并维护我的代码以及我的雇主的道德责任,使这个过程变得简单而且价格便宜。我看到这两种模式的混搭很容易变成一种难以维护的老鼠洞,这种老鼠难以维持。
答案 1 :(得分:6)
经典的做法是遵循Command Pattern。
您可以使用命令封装执行操作的任何对象,并让它使用Undo()方法执行反向操作。您可以将所有操作存储在堆栈中,以便通过它们轻松地进行重绕。
答案 2 :(得分:5)
这里有三种可行的方法。纪念模式(快照),命令模式和状态差异。它们都有优点和缺点。
我会选择State Diffing,因为它可以将内存减少与易于实现和可维护性相结合。
请注意,文章中提到的VoxelShop是开源的。所以你可以在这里看一下命令模式的复杂性: https://github.com/simlu/voxelshop/tree/develop/src/main/java/com/vitco/app/core/data/history
以下摘自文章:
Memento Pattern
赞成
缺点
命令模式
赞成
缺点
状态差异
赞成
缺点/限制
参考:
https://www.linkedin.com/pulse/solving-history-hard-problem-lukas-siemon
答案 3 :(得分:2)
看看Command Pattern。 您必须将模型的每个更改封装到单独的命令对象中。
答案 4 :(得分:0)
我写了一个非常灵活的系统来跟踪变化。我有一个绘图程序,它实现了两种类型的更改:
基类:
public abstract class Actie
{
public Actie(Vorm[] Vormen)
{
vormen = Vormen;
}
private Vorm[] vormen = new Vorm[] { };
public Vorm[] Vormen
{
get { return vormen; }
}
public abstract void Undo();
public abstract void Redo();
}
用于添加形状的派生类:
public class VormenToegevoegdActie : Actie
{
public VormenToegevoegdActie(Vorm[] Vormen, Tekening tek)
: base(Vormen)
{
this.tek = tek;
}
private Tekening tek;
public override void Redo()
{
tek.Vormen.CanRaiseEvents = false;
tek.Vormen.AddRange(Vormen);
tek.Vormen.CanRaiseEvents = true;
}
public override void Undo()
{
tek.Vormen.CanRaiseEvents = false;
foreach(Vorm v in Vormen)
tek.Vormen.Remove(v);
tek.Vormen.CanRaiseEvents = true;
}
}
用于删除形状的派生类:
public class VormenVerwijderdActie : Actie
{
public VormenVerwijderdActie(Vorm[] Vormen, Tekening tek)
: base(Vormen)
{
this.tek = tek;
}
private Tekening tek;
public override void Redo()
{
tek.Vormen.CanRaiseEvents = false;
foreach(Vorm v in Vormen)
tek.Vormen.Remove(v);
tek.Vormen.CanRaiseEvents = true;
}
public override void Undo()
{
tek.Vormen.CanRaiseEvents = false;
foreach(Vorm v in Vormen)
tek.Vormen.Add(v);
tek.Vormen.CanRaiseEvents = true;
}
}
属性更改的派生类:
public class PropertyChangedActie : Actie
{
public PropertyChangedActie(Vorm[] Vormen, string PropertyName, object OldValue, object NewValue)
: base(Vormen)
{
propertyName = PropertyName;
oldValue = OldValue;
newValue = NewValue;
}
private object oldValue;
public object OldValue
{
get { return oldValue; }
}
private object newValue;
public object NewValue
{
get { return newValue; }
}
private string propertyName;
public string PropertyName
{
get { return propertyName; }
}
public override void Undo()
{
//Type t = base.Vorm.GetType();
PropertyInfo info = Vormen.First().GetType().GetProperty(propertyName);
foreach(Vorm v in Vormen)
{
v.CanRaiseVeranderdEvent = false;
info.SetValue(v, oldValue, null);
v.CanRaiseVeranderdEvent = true;
}
}
public override void Redo()
{
//Type t = base.Vorm.GetType();
PropertyInfo info = Vormen.First().GetType().GetProperty(propertyName);
foreach(Vorm v in Vormen)
{
v.CanRaiseVeranderdEvent = false;
info.SetValue(v, newValue, null);
v.CanRaiseVeranderdEvent = true;
}
}
}
每次 Vormen =提交更改的项目数组。 它应该像这样使用:
堆栈声明:
Stack<Actie> UndoStack = new Stack<Actie>();
Stack<Actie> RedoStack = new Stack<Actie>();
添加新形状(例如Point)
VormenToegevoegdActie vta = new VormenToegevoegdActie(new Vorm[] { NieuweVorm }, this);
UndoStack.Push(vta);
RedoStack.Clear();
删除所选形状
VormenVerwijderdActie vva = new VormenVerwijderdActie(to_remove, this);
UndoStack.Push(vva);
RedoStack.Clear();
注册房产变更
PropertyChangedActie ppa = new PropertyChangedActie(new Vorm[] { (Vorm)e.Object }, e.PropName, e.OldValue, e.NewValue);
UndoStack.Push(ppa);
RedoStack.Clear();
最后是撤消/重做动作
public void Undo()
{
Actie a = UndoStack.Pop();
RedoStack.Push(a);
a.Undo();
}
public void Redo()
{
Actie a = RedoStack.Pop();
UndoStack.Push(a);
a.Redo();
}
我认为这是实现undo-redo算法的最有效方法。 例如,请查看我的网站上的此页面:DrawIt。
我在Tekening.cs文件的第479行附近实现了undo redo stuff。您可以下载源代码。它可以通过任何类型的应用程序实现。