好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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 设计模式》 6  封装调用:命令模式

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

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

《 Getting started with Google C++ Testing Framework 》

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Undo/Redo框架(C++,带源码)的详细内容...

  阅读:42次

CopyRight:2016-2025好得很程序员自学网 备案ICP:湘ICP备09009000号-16 http://www.haodehen.cn
本站资讯不构成任何建议,仅限于个人分享,参考须谨慎!
本网站对有关资料所引致的错误、不确或遗漏,概不负任何法律责任。
本网站刊载的所有内容(包括但不仅限文字、图片、LOGO、音频、视频、软件、程序等)版权归原作者所有。任何单位或个人认为本网站中的内容可能涉嫌侵犯其知识产权或存在不实内容时,请及时通知本站,予以删除。

网站内容来源于网络分享,如有侵权发邮箱到:kenbest@126.com,收到邮件我们会即时下线处理。
网站框架支持:HDHCMS   51LA统计 百度统计
Copyright © 2018-2025 「好得很程序员自学网
[ SiteMap ]