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++,带源码)的详细内容...