1

我正在尝试重构我拥有的撤消/重做实现,但不确定如何去做。

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> 中,该 Stack<UndoRedoAction> 用作撤消缓存。更具体地说,有两个堆栈,一个用于撤消,一个用于重做,从一个弹出的操作被推送到另一个。此外,针对 Zaid Masud 的回应,变量不一定都是整数,甚至不一定都是相同的对象类型。我的例子只是为了简单起见。

4

3 回答 3

2

您可以通过使用动态方法或表达式来避免反射,以基于选择器表达式(如x => x.SomeProperty.

基本上你想做这样的事情:

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 表达式,但这是一个已知已解决的问题。有关如何执行此操作的问题,请参见此处此处的示例。

在这种方法中,从表达式编译 getter/setter 委托的成本仅在您创建操作提供程序时发生一次,而不是每次您创建操作或调用RedoUndo在一个操作时。

如果您想进一步优化,您可以propertySelector在操作构造函数中移动参数,并在后台按需创建操作提供程序并基于每个属性进行缓存。这将产生一些更容易使用的代码,但实现起来可能更棘手。

希望这可以帮助!

于 2012-10-14T21:46:17.027 回答
1

对于这种问题,我更喜欢使用Stack<T>orList<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 { }
于 2012-10-14T17:54:09.910 回答
0

我想如果其中的所有字段MyObjectint与您的代码示例中的一样,您可以将它们转换为固定长度的数组。代码示例(未编译)

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];
    }
}
于 2012-10-14T17:41:30.320 回答