Undo/Redo框架(C++,带源码)

#pragma once



#include "UndoRedo\BaseCommandReceiver.h"



class Invoker;



class MockCommandReceiver : public BaseCommandReceiver

{

public:

    MockCommandReceiver();

    ~MockCommandReceiver();



    virtual bool Action(bool bUndo);



    void PrepareData(Invoker * pInvoker, int nParameter);



public:

    int m_nData;

    Invoker * m_pInvoker;

};


#include "StdAfx.h"

#include <iostream>

#include "MockCommandReceiver.h"

#include "Invoker.h"



RegisterCommandReceiverClass<MockCommandReceiver> RegisterCommandReceiverClass(ClassNameToString(MockCommandReceiver));



MockCommandReceiver::MockCommandReceiver():

m_pInvoker(NULL),

m_nData(0)

{

}



MockCommandReceiver::~MockCommandReceiver()

{

}



bool MockCommandReceiver::Action(bool bUndo)

{

    if (bUndo)

    {

        if (!m_pInvoker)

        {

            return false;

        }

        else

        {

            m_pInvoker->PopElement();

        }

    }

    else

    {

        if (!m_pInvoker)

        {

            return false;

        }

        else

        {

            m_pInvoker->PushElement(m_nData);

        }

    }



    return true;

}



void MockCommandReceiver::PrepareData(Invoker * pInvoker, int nParameter)

{

    m_pInvoker = pInvoker;

    m_nData = nParameter;

}


 

下面的测试用例中,有个对命令执行失败情况的测试,所以声明MockCommand来模拟执行成功和失败。

 

#pragma once



#include "UndoRedo\BaseCommand.h"



class MockCommand : public BaseCommand

{

public:

    MockCommand();

    virtual ~MockCommand();



    virtual bool Execute();

    virtual bool Unexecute();



    void PrepareData(bool bReturnTrue);



private:

    bool m_bReturnTrue;

};


#include "StdAfx.h"

#include <iostream>

#include "MockCommand.h"



RegisterCommandClass<MockCommand> RegisterCommandClass(ClassNameToString(MockCommand));



MockCommand::MockCommand():

m_bReturnTrue(true)

{

}



MockCommand::~MockCommand()

{

}



bool MockCommand::Execute()

{

    // 在此增加命令的执行代码

    std::cout << "Mock command is executing. Return " << (m_bReturnTrue?"true":"false") << ".\n\n";

    return m_bReturnTrue;

}



bool MockCommand::Unexecute()

{

    // 在此增加命令的撤销代码

    std::cout << "Mock command is unexecuting. Return " << (m_bReturnTrue?"true":"false") << ".\n\n";

    return m_bReturnTrue;

}



void MockCommand::PrepareData(bool bReturnTrue)

{

    m_bReturnTrue = bReturnTrue;

}


 

要测试的内容包括:

 

1.       简单命令的调用、撤销和恢复

2.       组合命令的调用、撤销和恢复

3.       清除所有命令

4.       在撤销一个命令后调用另一个命令

5.       失败的命令调用、撤销和恢复

6.       大量的命令调用、撤销和恢复

7.       以上操作后,Undoable/Redoable的状态

 

每个用例的目的、步骤和期望结果就不赘述了,看代码吧。

 

TEST_F(Invoker, TestUndoRedoFramework)

{

    std::cout << "----- Test simple command and undo/redo -----\n\n";



    std::cout << "Execute\n";

    int nElement1 = 1;

    CALLCOMMAND(ConstructCommand(nElement1));

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);

    int expect = 1;

    int actual = m_listElements.size();

    ASSERT_EQ(expect, actual);

    expect = nElement1;

    std::list<int>::const_iterator iter = m_listElements.begin();

    actual = *iter;

    ASSERT_EQ(expect, actual);



    std::cout << "Execute\n";

    int nElement2 = 2;

    CALLCOMMAND(ConstructCommand(nElement2));

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);

    expect = 2;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);

    expect = nElement1;

    iter = m_listElements.begin();

    actual = *iter;

    ASSERT_EQ(expect, actual);

    expect = nElement2;

    actual = *(++iter);

    ASSERT_EQ(expect, actual);



    std::cout << "Undo\n";

    UNDO;

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_TRUE(CANREDO);

    expect = 1;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);



    std::cout << "Redo\n";

    REDO;

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);

    expect = 2;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);

    expect = nElement1;

    iter = m_listElements.begin();

    actual = *iter;

    ASSERT_EQ(expect, actual);

    expect = nElement2;

    actual = *(++iter);

    ASSERT_EQ(expect, actual);



    std::cout << "Undo twice\n";

    UNDO;

    UNDO;

    DisplayList();



    ASSERT_FALSE(CANUNDO);

    ASSERT_TRUE(CANREDO);

    expect = 0;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);



    std::cout << "Redo twice\n";

    REDO;

    REDO;

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);

    expect = 2;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);

    expect = nElement1;

    iter = m_listElements.begin();

    actual = *iter;

    ASSERT_EQ(expect, actual);

    expect = nElement2;

    actual = *(++iter);

    ASSERT_EQ(expect, actual);



    std::cout << "----- Test clear all commands -----\n\n";



    std::cout << "Clear all commands\n";

    CLEARALLCOMMANDS;



    ASSERT_FALSE(CANUNDO);

    ASSERT_FALSE(CANREDO);



    std::cout << "----- Test macro command -----\n\n";



    CLEARALLCOMMANDS;

    ClearAllElements();



    std::cout << "Execute\n";

    MacroCommand * pMacroCommand = (MacroCommand *)CREATECOMMAND(MacroCommand);

    int nElement3 = 3;

    pMacroCommand->AddCommand(ConstructCommand(nElement3));

    int nElement4 = 4;

    pMacroCommand->AddCommand(ConstructCommand(nElement4));

    int nElement5 = 5;

    pMacroCommand->AddCommand(ConstructCommand(nElement5));

    CALLCOMMAND(pMacroCommand);

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);

    expect = 3;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);



    std::cout << "Undo\n";

    UNDO;

    DisplayList();



    ASSERT_FALSE(CANUNDO);

    ASSERT_TRUE(CANREDO);

    expect = 0;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);



    std::cout << "Redo\n";

    REDO;

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);

    expect = 3;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);

    std::vector<int> vecElements;

    vecElements.push_back(nElement3);

    vecElements.push_back(nElement4);

    vecElements.push_back(nElement5);

    int i = 0;

    for (iter = m_listElements.begin(); iter != m_listElements.end(); iter++, i++)

    {

        expect = vecElements[i];

        actual = *iter;

        ASSERT_EQ(expect, actual);

    }



    std::cout << "----- Test command called after undo -----\n\n";



    CLEARALLCOMMANDS;

    ClearAllElements();



    std::cout << "Execute\n";

    int nElement6 = 6;

    CALLCOMMAND(ConstructCommand(nElement6));

    DisplayList();

    std::cout << "Undo\n";

    UNDO;

    DisplayList();

    std::cout << "Execute\n";

    int nElement7 = 7;

    CALLCOMMAND(ConstructCommand(nElement7));

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);

    expect = 1;

    actual = m_listElements.size();

    ASSERT_EQ(expect, actual);

    expect = nElement7;

    iter = m_listElements.begin();

    actual = *iter;

    ASSERT_EQ(expect, actual);



    std::cout << "----- Test failed command and undo/redo -----\n\n";



    CLEARALLCOMMANDS;

    ClearAllElements();



    MockCommand * pMockCommand = (MockCommand *)CREATECOMMAND(MockCommand);

    pMockCommand->PrepareData(true);

    std::cout << "Execute\n";

    CALLCOMMAND(pMockCommand);

    std::cout << "Undo\n";

    UNDO;

    std::cout << "Redo\n";

    REDO;



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);



    pMockCommand->PrepareData(false);

    std::cout << "Undo\n";

    UNDO;



    ASSERT_FALSE(CANUNDO);

    ASSERT_FALSE(CANREDO);



    pMockCommand = (MockCommand *)CREATECOMMAND(MockCommand);

    pMockCommand->PrepareData(true);

    std::cout << "Execute\n";

    CALLCOMMAND(pMockCommand);

    std::cout << "Undo\n";

    UNDO;



    ASSERT_FALSE(CANUNDO);

    ASSERT_TRUE(CANREDO);



    pMockCommand->PrepareData(false);

    std::cout << "Redo\n";

    REDO;



    ASSERT_FALSE(CANUNDO);

    ASSERT_FALSE(CANREDO);



    std::cout << "----- Test lots of commands and undo/redo -----\n\n";



    CLEARALLCOMMANDS;



    const int nCount = 300;

    for (i = 0; i < nCount; i++)

    {

        CALLCOMMAND(ConstructCommand(i));

    }

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);



    for (i = 0; i < nCount; i++)

    {

        UNDO;

    }

    DisplayList();



    ASSERT_FALSE(CANUNDO);

    ASSERT_TRUE(CANREDO);



    for (i = 0; i < nCount; i++)

    {

        REDO;

    }

    DisplayList();



    ASSERT_TRUE(CANUNDO);

    ASSERT_FALSE(CANREDO);



    CLEARALLCOMMANDS;



    ASSERT_FALSE(CANUNDO);

    ASSERT_FALSE(CANREDO);

}


 

后记

 

有人说:“你罗罗嗦嗦地说这么多,不就是个Undo/Redo框架么,至于这么费劲么?”不错,说得确实有点罗嗦。不过,在实际的工作中,对以上每一个技术细节的思考都是不可缺少的。当你的代码将被别人使用的时候,多费点精力在稳定性、可复用性、可扩展性等方面,还是很值得的。

 

以上内容,如有谬误,敬请指出,先谢过了!

 

请点击此处下载源代码

 

参考资料

 

《设计模式 - 可复用面向对象软件的基础》5.2 Command(命令)对象行为型模式

Head First设计模式》封装调用:命令模式

《敏捷软件开发 原则、模式与实践(C#版)》第21 COMMAND模式

C++设计新思维》部分章节

Getting started with Google C++ Testing Framework

你可能感兴趣的:(undo)