使用LinkedList <action>实现</action>撤消/重做功能

时间:2011-02-28 20:48:02

标签: c# linked-list action rubiks-cube

我正在编写自己的'魔方'应用程序。主类Cube有18种旋转方法:

  • RotateAxisXClockWise,RotateAxisXAntiClockWise
  • RotateAxisYClockWise,RotateAxisYAntiClockWise
  • RotateAxisZClockWise,RotateAxisZAntiClockWise

  • RotateUpperFaceClockWise,RotateUpperFaceAntiClockWise

  • RotateFrontFaceClockWise,RotateFrontFaceAntiClockWise
  • RotateRightFaceClockWise,RotateRightFaceAntiClockWise
  • RotateBackFaceClockWise,RotateBackFAceAntiClockWise
  • RotateLeftFaceClockWise,RotateLeftFaceAntiClockWise
  • RotateDownFaceClockWise,RotateDownFaceAntiClockWise

是的,它们可以成对加入一个参数Direction(例如RotateFrontFace(Direction direction)),但是现在这看似合适。

我想实现撤消/重做功能,因为所有方法都具有相同的签名(没有输入参数,void返回类型),所以它们可以保存在LinkedList数据结构中。因此,每次调用其中一个旋转方法时,都会将其添加到链接列表中。

如果我们从LinkedList的开头开始(虽然还没有尝试过)并且向前推进,这将非常有效,因此每次旋转都将完全按照首先执行。

但撤消呢?如果我遍历列表从结尾到beginnig,那么应该调用相反的方法(例如,而不是RotateFrontFaceClockWise,应该调用RotateFrontFaceAntiClockWise)。任何想法如何实现这一点?优雅? :)

4 个答案:

答案 0 :(得分:1)

如果其中一个主要目的是能够执行重做/撤消,我不会使用委托引用作为轮换建模的方法。我会考虑为每个旋转创建一个数据模型,并存储这些旋转步骤的列表。然后,每个步骤都可以拥有自己的相关重做/撤消委托,允许有人遍历列表(从任一端)来了解发生了什么操作,并重复或反转它们。

以数据为导向的方法来模拟此类转换的另一个好处是,它可能会减少RotateXXX( )方法的类似(但略有不同)版本的数量。

编辑:解决有关此类解决方案可能采取何种形式的问题。

最简单的方法是将Tuple<Action,Action>存储为代表每对旋转/非旋转操作作为配对代理。但是,我会考虑使用描述旋转操作的显式数据结构,最终可能包括描述性名称,方向/面部属性等内容。我还会更改您的RotateXXX方法,使它们是Cube的静态方法,并接受多维数据集的实例作为参数。这将允许在Cube的实例外部对旋转操作进行建模。

public sealed class Rotation
{
    private readonly Action<Cube> _RotateAction;
    private readonly Action<Cube> _UnrotateAction;  // used for undo or backtracking

    private Rotation( Action<Cube> rotateAction, Action<Cube> unrotateAction )
    {
        _RotateAction = rotateAction;
        _UnrotateAction = unrotateAction;
    }

    public void Rotate( Cube cube )   { _RotateAction( cube ); }

    public void Unrotate( Cube cube ) { _Unrotate( cube ); }

    public static readonly RotateFrontFaceClockswise = 
        new Rotation( Cube.RotateFrontFaceClockwise
                      Cube.RotateFrontFaceCounterClockwise );

    public static readonly RotateFrontFaceCounterClockwise = 
        new Rotation( Cube.RotateFrontFaceCounterClockwise,
                      Cube.RotateFrontFaceClockwise );

    public static readonly RotateLeftFaceClockwise = 
        new Rotation( Cube.RotateLeftFaceClockwise,
                      Cube.RotateLeftFaceCounterClockwise );

    public static readonly RotateLeftFaceCounterClockwise = 
        new Rotation( Cube.RotateLeftFaceCounterClockwise,
                      Cube.RotateLeftFaceClockwise );
    // etc..
}

// now we can keep track of the state changes of a cube using:
List<Rotation> cubeRotations = new List<Rotation>();
cubeRotations.Add( Rotation.RotateFrontFaceCounterClockwise );
cubeRotations.Add( Rotation.RotateBackFaceClockwise );
cubeRotations.Add( Rotation.RotateLeftFaceCounterClockwise );

// to apply the rotations to a cube, you simple walk through the data structure
// calling the Rotate( ) method on each:
Cube someCube = new Cube( ... )
foreach( Rotation r in cubeRotations )
{
    r.Rotate( someCube );
}

// to undo these rotations you can walk the like in reverse:
foreach( Rotation r in cubeRotations.Reverse() )
{
    r.Unrotate( someCube );
}

答案 1 :(得分:1)

我知道你想避免参数化你的方法,但你可能最好不要追求这些方面:

enum Face
{
    Top,
    Bottom,
    Left,
    Right,
    Front,
    Back
}

enum Direction
{
    Clockwise,
    CounterClockwise
}

struct Rotation
{
     public Face Face;
     public Direction Direction;
}

LinkedList<Rotation> actions;

您可以选择是将直接操作还是反向操作推送到列表中,但是一旦以这种方式存储操作,就很容易编写一些快速切换语句来反转操作,因为它从列表中删除

在旁注中,考虑用Stack替换LinkedList,它也会为您提供服务,并且它非常适合此目的。

编辑:

注意到我的枚举中没有轴旋转的支持,但我确定你可以在Face枚举中添加它们作为附加条目(尽管你可能希望在那时重命名)

答案 2 :(得分:1)

18个方法似乎要跟上很多,特别是在考虑实现撤消/重做功能时。您是否考虑过使用单一,更通用的方法?如果这样做,您可以以非常统一的方式存储传递给该方法的内容,并轻松执行相反的操作。可以从字典中查找相反的动作。

答案 3 :(得分:0)

假设你真的想坚持你的模特......

您可以尝试在列表中输入所执行方法的每个名称,然后通过反射,通过迭代列表来调用计数器部分。这假设您的方法将具有匹配的名称(如您所建议的那样)。

您还可以创建一个HybridDictionary,您可以将方法名称作为计数器方法的ID和地址作为值。因此,当您遍历列表时,您可以让委托处理给定值的方法。