• 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

  • 相关阅读:
    linux目录结构
    php程序员要懂那些linux知识?
    树和二叉树
    linux学习课程
    顺序栈的实现
    编写一个插件(前面JavaScript高级总结)
    javascript高级课程-4
    字符串的顺序表
    js 万年历实现
    利用 postMessage 进行数据传递 (iframe 及web worker)及问题
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2168163.html
Copyright © 2020-2023  润新知