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://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于Undo/Redo框架(C++,带源码)的详细内容...