#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++设计新思维》部分章节