我正在尝试重构我所拥有的撤消/重做实现但不确定如何去实现它。
public class MyObject
{
public int A;
public int B;
public int C;
}
public abstract class UndoRedoAction
{
protected MyObject myobj;
protected int oldValue;
protected int newValue;
public abstract void Undo();
public abstract void Redo();
}
public class UndoRedoActionA : UndoRedoAction
{
UndoRedoActionA(MyObject obj, int new)
{
myobj = obj;
oldValue = myobj.A;
newValue = new;
myobj.A = newValue;
}
public override void Undo()
{
myobj.A = oldValue;
}
public override void Redo()
{
myobj.A = newValue;
}
}
public class UndoRedoActionB : UndoRedoAction
{
UndoRedoActionB(MyObject obj, int new)
{
myobj = obj;
oldValue = myobj.B;
newValue = new;
myobj.B = newValue;
}
public override void Undo()
{
myobj.B = oldValue;
}
public override void Redo()
{
myobj.B = newValue;
}
}
public class UndoRedoActionC : UndoRedoAction
{
UndoRedoActionC(MyObject obj, int new)
{
myobj = obj;
oldValue = myobj.C;
newValue = new;
myobj.C = newValue;
}
public override void Undo()
{
myobj.C = oldValue;
}
public override void Redo()
{
myobj.C = newValue;
}
}
显然,每个UndoRedoAction子类在访问不同字段时都具有自定义功能,但它们在这些字段上执行的功能是相同的。有没有什么干净的方法,没有将这些内容写入属性和传递属性名称(我宁愿不做,魔术字符串等),将这些组合成一个通用的UndoRedoAction,而不是制作一堆所有执行的子类对不同变量采取完全相同的行动?
我确实考虑过使用可以解决问题的Memento模式,但是对于这么小的场景而言似乎相当过分,我没有单向行动担心,这是Memento模式真正有用的地方。
感谢。
澄清:这些UndoRedoAction对象放置在Stack< UndoRedoAction>内。用作撤消缓存。更具体地说,有两个堆栈,一个用于撤销,一个用于重做,弹出一个的动作被推到另一个上。另外,为了回应Zaid Masud的回应,变量不一定都是整数,甚至不是所有相同的对象类型。我的例子仅仅是为了简单起见。
答案 0 :(得分:2)
您可以使用动态方法或表达式来避免反射,以根据x => x.SomeProperty
之类的选择器表达式创建自定义属性getter / setter。
基本上你想要做这样的事情:
public class PropertySetActionProvider<TObj, TProp>
{
private Func<TObj, TProp> _getter;
private Action<Tbj, TProp> _setter;
public PropertySetActionProvider(Expression<Func<TObj, TProp>> propertySelector)
{
_getter = propertySelector.Compile();
_setter = SetterExpressionFromGetterExpression(propertySelector).Compile();
}
public IUndoRedoAction CreateAction(TObj target, TProp newValue)
{
var oldValue = _getter(target);
return new PropertySetAction<TObj, TProp>(_setter, target, oldValue, newValue);
}
}
public class PropertySetAction<TObj, TProp> : IUndoRedoAction
{
private Action<TObj, TProp> _setter;
private TObj _target;
private TProp _oldValue;
private TProp _newValue;
public PropertySetAction(Action<TObj, TProp> setter, TObj target, TProp oldValue, TProp newValue)
{
_setter = setter;
_target = target;
_oldValue = oldValue;
_newValue = newValue;
}
public void Do()
{
_setter(_target, _newValue);
}
public void Undo()
{
_setter(_target, _oldValue);
}
}
然后,您可以使用以下代码轻松创建新操作:
// create the action providers once per property you'd like to change
var actionProviderA = new PropertySetActionProvider<MyObject, int>(x => x.A);
var actionProviderB = new PropertySetActionProvider<MyObject, string>(x => x.B);
var someObject = new MyObject { A = 42, B = "spam" };
actions.Push(actionProviderA.CreateAction(someObject, 43);
actions.Push(actionProviderB.CreateAction(someObject, "eggs");
actions.Push(actionProviderA.CreateAction(someObject, 44);
actions.Push(actionProviderB.CreateAction(someObject, "sausage");
// you get the point
唯一困难的部分是从getter表达式(上面的SetterExpressionFromGetterExpression
方法)创建一个setter表达式,但这是一个已知的已解决问题。有关如何执行此操作的问题,请参阅here和here。
在此方法中,从表达式编译getter / setter委托的成本仅在您创建操作提供程序时产生,而不是每次创建操作或调用Redo
或Undo
时一。
如果要进一步优化,可以在动作构造函数中移动propertySelector
参数,并根据需要在幕后创建动作提供程序,并在每个属性的基础上进行缓存。这将使代码更容易使用,但实现起来可能比较棘手。
希望这有帮助!
答案 1 :(得分:1)
对于这类问题,我更喜欢使用Stack<T>
或List<T>
并将整个对象复制到其中。这允许具有比一个更多的撤销缓存,并且它是直截了当的。我使用此模型允许用户10撤消复杂对象。
顺便说一下,这需要一个可序列化的类使用。这是我的代码,用于制作我的课程的精确副本:
private T DeepCopy<T>(T obj)
{
object result = null;
if (obj == null)
{
return (T)result;
}
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms);
ms.Close();
}
return (T)result;
}
编辑:用于List的简单用法
List<MyClass> UndoCache = new List<MyClass>();
MyClass myRealObject;
int unDoCount = 10;
void Undo()
{
myRealObject = UndoCache.Last();
//remove this object from cache.
UndoCache.RemoveAt(UndoCache.Count - 1);
}
//在更改对象时调用此方法
void ObjectChanged()
{
//remove the first item if we reach limit
if (UndoCache.Count > unDoCount)
{
UndoCache.RemoveAt(0);
}
UndoCache.Add(DeepCopy<MyClass>(myRealObject));
}
public class MyClass { }
答案 2 :(得分:0)
我想如果代码示例中的MyObject
中的所有字段都是int
,则可以将它们转换为固定长度的数组。代码示例(未编译)
public class MyObject
{
public const int ARRAY_LENGTH = 3;
public int[] Ints = new int[ARRAY_LENGTH]
}
public abstract class UndoRedoAction
{
private MyObject myobj;
private int oldValues = new int[MyObject.ARRAY_LENGTH];
private int newValues = new int[MyObject.ARRAY_LENGTH];
public UndoRedoAction(MyObject obj)
{
myobj = obj;
}
public void SetValue(int index, int newValue)
{
oldValues[index] = myObj.Ints[index];
newValues[index] = newValue;
myObj.Ints[index] = newValue;
}
public void Undo(int index)
{
myObj.Ints[index] = oldValues[index];
}
public void Redo(int index)
{
myObj.Ints[index] = newValues[index];
}
}