Fork me on GitHub
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

posted on 2011-09-05 23:39  HackerVirus  阅读(529)  评论(0)    收藏  举报