多个变量的撤消/重做实现

时间:2012-10-14 17:31:58

标签: c# inheritance command undo-redo memento

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

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的回应,变量不一定都是整数,甚至不是所有相同的对象类型。我的例子仅仅是为了简单起见。

3 个答案:

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

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

如果要进一步优化,可以在动作构造函数中移动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];
    }
}