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 子クラスのそれぞれは、異なるフィールドにアクセスするためのカスタム機能を備えていますが、これらのフィールドに対して実行する機能は同じです。これらの int をプロパティにしてプロパティ名を渡す以外に (私はやりたくない、魔法の文字列など)、すべてを実行する子クラスの束を作成する代わりに、これらを汎用の UndoRedoAction に結合するクリーンな方法はありますか?異なる変数に対するまったく同じアクション?

この問題を解決する Memento パターンの使用を検討しましたが、これほど小さなシナリオではやり過ぎのように思えますし、心配する必要のある一方通行のアクションもありません。Memento パターンが本当に役立つのはこのような場合です。

ありがとう。

明確化:これらの UndoRedoAction オブジェクトは、元に戻すキャッシュとして機能する Stack<UndoRedoAction> 内に配置されます。より具体的には、2 つのスタックがあり、1 つは取り消し用、もう 1 つはやり直し用であり、一方からポップされたアクションは他方にプッシュされます。さらに、Zaid Masud の回答に応じて、変数は必ずしもすべて int ではなく、すべて同じオブジェクト型でさえありません。私の例では、簡単にするためにそれを行っただけです。

4

3 に答える 3

2

動的メソッドまたは式を使用して、のようなセレクター式に基づいてカスタム プロパティの getter/setter を作成することにより、リフレクションを回避できます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 式から setter 式を作成すること (SetterExpressionFromGetterExpression上記の方法) ですが、これは既知の解決済みの問題です。その方法に関する質問については、たとえばここここを参照してください。

Redoこのアプローチでは、式から getter/setter デリゲートをコンパイルするコストは、アクションまたは呼び出しを作成するたびに発生するのではなく、アクション プロバイダーを作成するときに 1 回だけ発生しますUndo

さらに最適化する場合は、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;
    }

EDIT: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 に答える