Undo/Redo框架(C++,带源码)
http://www.cnblogs.com/wanghui9072229/archive/2011/08/29/2158960.html
目录
前言
框架设计
代码实现
单元测试
后记
参考资料
前言
终于结束赋闲在家的状态,又走上研发经理的岗位。老板“教导”我说:“作为‘空降’的管理者,要想得到团队中其他成员的信任和认可,必须身先士卒,去解决开发中难题。”言下之意很明显,得先干Hands-on的工作。于是我便有了做现有系统图形操作的撤销和恢复(Undo/Redo)功能的任务,因为这项工作被其他人认为是比较难啃的骨头(原因是你要在现有功能的实现代码中加入这个 Undo/Redo,而这些代码是由多人写的,要读懂它们就得费不少功夫,最多的一个操作2000多行代码,还不算间接调用的函数)。
当然,实现具体功能的Undo/Redo之前,首先要搭建一套Undo/Redo的框架。不过好在实现Undo/Redo框架还不是那么复杂。以前看到过一些关于如何实现Undo/Redo功能的书和网页,带过的团队也曾做过Undo/Redo,但是自己亲自下手,还是头一回。下面就把设计、实现和测试的过程回顾一下,算是做个总结。
框架设计
一、最基本的,当然是使用命令(Command)设计模式。见下面的类图:
如果用C#,可能用接口(interface)来定义它们比较好,比如定义ICommand和ICommandManager。但C++中没有interface,所以用抽象类(Abstract Class)来实现,所有方法都声明为纯虚函数。
至于Command设计模式,无需多说,无非这里的Command 模式带Undo/Redo功能。
二、下一步当然是BaseCommandManager的实现子类CommandManager,见下面的类图:
CommandManager内部会维护着2个栈(Undo Stack和Redo Stack),并增加相应的操作栈的私有方法,如:PushUndoCommand、PopUndoCommand等。CommandManager基于这个数据结构来实现BaseCommandManager声明的所有纯虚函数。
三、下面是BaseCommand的实现子类Command。根据《Head First设计模式》里说得,这里有两种方案,一种是“傻瓜式”的Command,即Command持有一个接收者(Receiver)的指针,所有具体的命令都由Receiver来处理,Command对如何处理命令一无所知,像“傻瓜”一样。另一种是“聪明”的Command,BaseCommand的子类知道如何处理命令并直接处理。这里我采用了“傻瓜式”的Command(当然此框架也支持“聪明”的Command,但Command子类需要根据客户程序的需要由框架的使用者自己来实现了),见下面类图:
BaseCommandReceiver也是个抽象类,声明一个纯虚函数Action。Command的Execute和Unexecute方法分别用false和true作为参数调用虚方法Action以执行命令。
客户程序在生成Command的同时,应该给其赋予一个BaseCommandReceiver的指针m_pReceiver。缺省地,Command销毁的时候会一并销毁m_pReceiver,但客户程序也可通过设置bAutoDelete为false,来自己销毁。
BaseCommandReceiver的子类负责实际的命令处理。
四、很多书中介绍Command设计模式的时候,都会提到组合命令。用组合命令可以实现命令的“批处理”。我们这里也需要,所以要实现BaseCommand的另一个子类MacroCommand。见下面类图:
MacroCommand维护一个Command的集合(这里用std::vector实现),客户程序可以添加命令(AddCommand)和删除命令(DeleteCommand)。当然MacroCommand也要实现BaseCommand的Execute和Unexecute函数,具体的实现就是遍历vector中的Command,逐个执行和逐个撤销。
五、支持Undo/Redo的应用程序,一般都有“Undo”和“Redo”两个按钮,那么当命令的Undo/Redo栈内容变化的时候,两个按钮会根据是否“可撤销”和“可恢复”相应地变化为Enabled或Disabled状态。那么框架支持这个功能是通过观察者(Observer)设计模式来完成的。Observer模式也无需多说,见下面的类图:
简单的说,当CommandManager的Undo和Redo栈由空变为不空时,或由不空变为空时,都会调用Subject的Notify函数通知所有观察者,来更新UI(比如:Undo/Redo按钮的Enabled/Disabled状态)。
六、这个框架支持的五个基本操作:执行命令、Undo、Redo、清除Undo/Redo历史记录、Undo/Redo状态改变的时序图依次如下:
代码实现
#pragma onceclass BaseCommand;class BaseCommandManager{public: virtual ~BaseCommandManager() {} virtual bool CallCommand(BaseCommand * pCommand) = 0; virtual void ClearAllCommands() = 0; virtual void Undo() = 0; virtual void Redo() = 0; virtual bool CanUndo() const = 0; virtual bool CanRedo() const = 0;};
#pragma once#include "Factory.h"class BaseCommand{public: virtual ~BaseCommand() {} virtual bool Execute() = 0; virtual bool Unexecute() = 0; static BaseCommand * CreateCommand(const std::string& strCommand) { return Factory<BaseCommand, const std::string>::instance()->CreateObject(strCommand); }};template <class DerivedCommand> class RegisterCommandClass{public: static BaseCommand * Create() { return new DerivedCommand; } RegisterCommandClass(const std::string& strId) { Factory<BaseCommand, const std::string>::instance()->Register(strId, RegisterCommandClass::Create); }};#define CREATECOMMAND(Command) BaseCommand::CreateCommand(ClassNameToString(Command))
在使用此框架时,可能会产生一些BaseCommand的子类。这里使用对象工厂(Object Factory)设计模式来实现BaseCommand子类的创建,目的是解除这些子类与框架的耦合。(关于对象工厂,参见本人另一篇博客《对象工厂设计模式》。Factory.h的代码见下)
#pragma once#include <map>#include <vector>#include <string>template< class AbstractProduct, class IndentifierType, typename ProductCreator = AbstractProduct* (*)()>class Factory{private: Factory() {} Factory(Factory& factory); Factory& operator=(const Factory& factory);public: bool Register(const IndentifierType& id, ProductCreator creator) { associations_[id] = creator; return true; } bool UnRegister(const IndentifierType& id) { return associations_.erase(id) == 1; } AbstractProduct * CreateObject(const IndentifierType& id) { typename AssocMap::const_iterator i = associations_.find(id); if (i != associations_.end()) { return (i->second)(); } return NULL; } std::vector<typename IndentifierType> Keys() { std::vector<IndentifierType> result; AssocMap::iterator itr = associations_.begin(); for (; itr!= associations_.end(); itr++) { result.push_back(itr->first); } return result; } static Factory* instance() { static Factory * pFactory = NULL; if (!pFactory) { static Factory factory; pFactory = &factory; } return pFactory; }private: typedef std::map<IndentifierType, ProductCreator> AssocMap; AssocMap associations_;};template <class AbstractProduct,class Product> class RegisterClassToFactory{public: static AbstractProduct * Create() { return new Product; } RegisterClassToFactory(const std::string& id) { Factory<AbstractProduct, std::string>::instance()->Register(id, RegisterClassToFactory::Create); }};#define ClassNameToString(x) #x
所以在BaseCommand中定义RegisterCommandClass模板类来支持BaseCommand子类向工厂的注册。并且在BaseCommand类里增加CreateCommand静态函数(包括宏定义CREATECOMMAND),通过工厂在运行时可以“动态”生成BaseCommand的子类。
#pragma once#include <stack>#include "BaseCommandManager.h"#include "Subject.h"#include "Singleton.h"#define EVENT_UNDOREDOSTATECHANGED 1class BaseCommand;class CommandManager : public BaseCommandManager, public Subject{ class UndoRedoStateInspector { friend class CommandManager; private: UndoRedoStateInspector(CommandManager * pCommandManager); ~UndoRedoStateInspector(); private: CommandManager * m_pCommandManager; bool m_bUndoable; bool m_bRedoable; }; friend class Singleton<CommandManager>;private: CommandManager(); ~CommandManager(); CommandManager(const CommandManager& rhs); CommandManager& operator=(const CommandManager& rhs);public: static CommandManager * Instance(); bool CallCommand(BaseCommand * pCommand); void ClearAllCommands(); void Undo(); void Redo(); bool CanUndo() const; bool CanRedo() const;private: void PushUndoCommand(BaseCommand * pCommand); BaseCommand * PopUndoCommand(); void PushRedoCommand(BaseCommand * pCommand); BaseCommand * PopRedoCommand(); void DeleteUndoCommands(); void DeleteRedoCommands();private: std::stack<BaseCommand *> m_stackUndo; std::stack<BaseCommand *> m_stackRedo;};#define CALLCOMMAND(Command) CommandManager::Instance()->CallCommand(Command)#define UNDO CommandManager::Instance()->Undo()#define REDO CommandManager::Instance()->Redo()#define CLEARALLCOMMANDS CommandManager::Instance()->ClearAllCommands();#define CANUNDO CommandManager::Instance()->CanUndo()#define CANREDO CommandManager::Instance()->CanRedo()
CommandManager.cpp
CommandManager实现为单件(这不是必须的,只不过我们的系统需要这样做)。这个单件由Singleton模板类实现(Singleton<CommandManager>::Instance())。顺便说一句,把Singleton做成模板类的好处是:单件有许多变种(Mayers单件、Phoenix单件、带寿命的单件和双检测锁定单件等),当需要修改单件的实现方法时,只需改这个模板类即可,不用每个单件类都去修改。Singleton模板类代码如下(这里Singleton不是线程安全的,需要加双检测锁定才能支持多线程):
#pragma oncetemplate <typename T>class Singleton{private: Singleton() {} Singleton(const Singleton& rhs); Singleton& operator=(const Singleton& rhs);public: static T * Instance() { static T * pT = NULL; if (!pT) { static T instance; pT = &instance; } return pT; }};
CommandManager里有个内嵌类UndoRedoStateInspector,它的作用相当于一个“门卫”,“守卫”在CommandManager的CallCommand、ClearAllCommands、Undo和Redo函数的“门口”,它可以在进入函数时(即构造UndoRedoStateInspector时)保存CanUndo和CanRedo的状态,当退出函数时(即析构UndoRedoStateInspector时),检查2个状态是否改变,如改变则通知观察者(Observer)们状态已改变。这个做法非常类似于多线程编程中经常使用的Lock对象(即在构造时获得Mutex,析构时释放Mutex)。
在CommandManager的最后还定义了几个宏CALLCOMMAND、UNDO、REDO等,目的是方便调用(可以使调用者少敲一些字符)。
#pragma once#include "BaseCommand.h"class BaseCommandReceiver;class Command : public BaseCommand{public: Command(); virtual ~Command(); virtual bool Execute(); virtual bool Unexecute(); void SetReceiver(BaseCommandReceiver * pReceiver, bool bAutoDelete = true);private: Command(const Command& rhs); Command& operator=(const Command& rhs);protected: BaseCommandReceiver * m_pReceiver; bool m_bAutoDeleteReceiver;};
#include "StdAfx.h"#include "Command.h"#include "BaseCommandReceiver.h"RegisterCommandClass<Command> RegisterCommandClass(ClassNameToString(Command));Command::Command(void):m_pReceiver(NULL),m_bAutoDeleteReceiver(true){}Command::~Command(void){ if (m_bAutoDeleteReceiver && m_pReceiver) { delete m_pReceiver; m_pReceiver = NULL; }}bool Command::Execute(){ if (m_pReceiver) { return m_pReceiver->Action(false); } return false;}bool Command::Unexecute(){ if (m_pReceiver) { return m_pReceiver->Action(true); } return false;}void Command::SetReceiver(BaseCommandReceiver * pReceiver, bool bAutoDelete/* = true*/){ m_pReceiver = pReceiver; m_bAutoDeleteReceiver = bAutoDelete;}
作为BaseCommand的子类,Command向工厂注册自己。
RegisterCommandClass<Command> RegisterCommandClass(ClassNameToString(Command));
在客户程序创建Command对象时,代码可能像下面这个样子:
Command * pCommand = (Command *)CREATECOMMAND(Command);
#pragma once#include <vector>#include "BaseCommand.h"class MacroCommand : public BaseCommand{public: MacroCommand(); ~MacroCommand(); virtual bool Execute(); virtual bool Unexecute(); void AddCommand(BaseCommand * pCommand); void DeleteCommand(BaseCommand * pCommand);private: MacroCommand(const MacroCommand& rhs); MacroCommand& operator=(const MacroCommand& rhs);private: std::vector<BaseCommand *> m_vecCommands;};
#include "StdAfx.h"#include <algorithm>#include "MacroCommand.h"#include "Util.h"RegisterCommandClass<MacroCommand> RegisterCommandClass(ClassNameToString(MacroCommand));MacroCommand::MacroCommand(){}MacroCommand::~MacroCommand(){ ContainerDeleter<std::vector<BaseCommand *>>(m_vecCommands);}bool MacroCommand::Execute(){ for (unsigned int i = 0; i < m_vecCommands.size(); i++) { BaseCommand * pCommand = m_vecCommands[i]; if (!pCommand->Execute()) { return false; } } return true;}bool MacroCommand::Unexecute(){ for (unsigned int i = m_vecCommands.size(); i > 0; i--) { BaseCommand * pCommand = m_vecCommands[i-1]; if (!pCommand->Unexecute()) { return false; } } return true;}void MacroCommand::AddCommand(BaseCommand * pCommand){ if (pCommand) { m_vecCommands.push_back(pCommand); }}void MacroCommand::DeleteCommand(BaseCommand * pCommand){ if (pCommand) { m_vecCommands.erase(std::remove(m_vecCommands.begin(), m_vecCommands.end(), pCommand)); }}
#include "stdafx.h"template <typename T>void ContainerDeleter(T& Container){ for (T::iterator iter = Container.begin(); iter != Container.end(); iter++) { delete (*iter); } Container.clear();}
同样,作为BaseCommand的子类,MacroCommand也需要向工厂注册自己。
RegisterCommandClass<MacroCommand> RegisterCommandClass(ClassNameToString(MacroCommand));
MacroCommand的析构函数要清理m_vecCommands,所以写了个ContainerDeleter。这个模板函数其实可以胜任任何支持迭代器的容器的清理工作(delete元素和clear容器)。
#pragma once#include "Factory.h"class BaseCommandReceiver{public: virtual ~BaseCommandReceiver() {} virtual bool Action(bool bUndo) = 0; static BaseCommandReceiver * CreateCommandReceiver(const std::string& strCommandReceiver) { return Factory<BaseCommandReceiver, const std::string>::instance()->CreateObject(strCommandReceiver); }};template <class DerivedCommandReceiver> class RegisterCommandReceiverClass{public: static BaseCommandReceiver * Create() { return new DerivedCommandReceiver; } RegisterCommandReceiverClass(const std::string& strId) { Factory<BaseCommandReceiver, const std::string>::instance()->Register(strId, RegisterCommandReceiverClass::Create); }};#define CREATECOMMANDRECEIVER(CommandReceiver) BaseCommandReceiver::CreateCommandReceiver(ClassNameToString(CommandReceiver))
在使用此框架时,可能会产生一些BaseCommandReceiver的子类。所以与BaseCommand类似,BaseCommandReceiver也使用对象工厂(Object Factory)设计模式来实现子类的创建。在BaseCommandReceiver中定义RegisterCommandReceiverClass模板类来支持BaseCommandReceiver子类向工厂的注册。并且在BaseCommandReceiver类里增加CreateCommandReceiver静态函数(包括宏定义CREATECOMMANDRECEIVER),通过工厂在运行时可以“动态”生成BaseCommandReceiver的子类。
最后是观察者设计模式(Subject和Observer)的代码,见下:
// Subject.h: interface for the Subject class.////#if !defined(AFX_SUBJECT_H__8F429A5F_6BFF_48FB_8E63_5D91105ABBFC__INCLUDED_)#define AFX_SUBJECT_H__8F429A5F_6BFF_48FB_8E63_5D91105ABBFC__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include <sstream>#include <map>class Observer;// Implement the Subject class for the Observer pattern;class Subject { friend class Observer;public: typedef std::multimap<unsigned int, Observer*> E2O; typedef std::pair<unsigned int, Observer*> EOPair; typedef E2O::iterator EOI; typedef std::pair<EOI, EOI> EOPairI; // Notify all observers in this subject, pObserverFrom is the one who generated the event; void Notify(Observer* pObserverFrom, unsigned int Event, const std::stringstream& Data);protected: Subject(); virtual ~Subject(); Subject(const Subject& rhs); Subject& operator=(const Subject& rhs); // clear out all the subject/observer relationship; void ClearObservers(); //Get Observer count int GetObserverCount() ;private: // Attach/Detach the pObserver to this subject; void AttachObserver(Observer* pObserver, unsigned int Event); void DetachObserver(Observer* pObserver, unsigned int Event); // detatch all EO pair that matches the pObbserver; void DetachObserver(Observer* pObserver); // determine if the pObserver has any event registered with this subject; bool IsObserverRegistered(Observer* pObserver);private: //ACE_Thread_Mutex m_Mutex; // The Event to Observer container; E2O m_Observers;};#endif // !defined(AFX_SUBJECT_H__8F429A5F_6BFF_48FB_8E63_5D91105ABBFC__INCLUDED_)
// Subject.cpp: implementation of the Subject class.////#include "stdafx.h"#include "Subject.h"#include "Observer.h"Subject::Subject(){}Subject::~Subject(){ ClearObservers();}void Subject::ClearObservers(){ E2O::iterator OI = m_Observers.begin(); while(OI != m_Observers.end()) { OI->second->RemoveSubject(this); OI = m_Observers.erase(OI); }}void Subject::AttachObserver(Observer* pObserver, unsigned int Event){ EOPairI observers = m_Observers.equal_range(Event); EOI eoi = observers.first; bool bAlreadyExist = false; while(eoi != observers.second) { Observer* pO = eoi->second; if(pO == pObserver) { bAlreadyExist = true; break; } ++eoi; } if (!bAlreadyExist) { m_Observers.insert(EOPair(Event, pObserver)); }}void Subject::DetachObserver(Observer* pObserver, unsigned int Event){ EOPairI observers = m_Observers.equal_range(Event); EOI eoi = observers.first; while(eoi != observers.second) { Observer* pO = eoi->second; if(pO == pObserver) { m_Observers.erase(eoi); break; } ++eoi; } // unregister the observer from this subject if there is no longer any pObserver registered with this subject; if(!IsObserverRegistered(pObserver)) { pObserver->RemoveSubject(this); }}void Subject::DetachObserver(Observer* pObserver){ E2O::iterator OI = m_Observers.begin(); while(OI != m_Observers.end()) { if(OI->second == pObserver) { m_Observers.erase(OI); OI = m_Observers.begin(); continue; } ++OI; } // unregister the observer from this subject as well; pObserver->RemoveSubject(this);}void Subject::Notify(Observer* pObserverFrom, unsigned int Event, const std::stringstream& Data){ EOPairI observers = m_Observers.equal_range(Event); EOI eoi = observers.first;/* while(eoi != observers.second) { Observer* pO = eoi->second; ++eoi; if(pO != pObserverFrom) { pO->Update(this, pObserverFrom, Event, Data); } }*/ if (eoi != observers.second) { EOI eoiLast = observers.second; --eoiLast; while (true) { if (eoi == eoiLast) { Observer* pO = eoi->second; if(pO != pObserverFrom) { pO->Update(this, pObserverFrom, Event, Data); } break; } else { Observer* pO = eoi->second; eoi++; if(pO != pObserverFrom) { pO->Update(this, pObserverFrom, Event, Data); } } } }}bool Subject::IsObserverRegistered(Observer* pObserver){ E2O::iterator OI = m_Observers.begin(); while(OI != m_Observers.end()) { if(OI->second == pObserver) { return true; } ++OI; } return false;}int Subject::GetObserverCount() { return m_Observers.size();}
// Observer.h: interface for the Observer class.////#if !defined(AFX_OBSERVER_H__8E1444BE_2F25_4EE1_AC1A_6C963256B129__INCLUDED_)#define AFX_OBSERVER_H__8E1444BE_2F25_4EE1_AC1A_6C963256B129__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include <sstream>#include <map>class Subject;class Observer { friend class Subject;public: // subclass must override this function to get the even handling to work; // pSubject - dispatched from which subject; // pObserverFrom - if this message is initiated from another observer, this will indicate who is it; // Event - the even number; // Data - all data related with the event number are encoded into this stringstream object; virtual bool Update(Subject* pSubject, Observer* pObserverFrom, unsigned int Event, const std::stringstream& Data) = 0; bool HasSubject() const {return !m_E2Subjects.empty();} // observer/unobserve the Event from pSubject; void Observe(Subject* pSubject, unsigned int Event); void UnObserve(Subject* pSubject, unsigned int Event);protected: Observer(); virtual ~Observer(); Observer(const Observer& rhs); Observer& operator=(const Observer& rhs); // call subject to notify other observers; void NotifySubject(unsigned int Event, const std::stringstream& Data); // clear out all the subject/observer relationship; void ClearSubjects();private: // remove the pSubject for this Observer; void RemoveSubject(Subject* pSubject); // refer to the subject object; typedef std::multimap<unsigned int, Subject*> E2S; typedef std::pair<unsigned int, Subject*> ESPair; typedef E2S::iterator ESI; typedef std::pair<ESI, ESI> ESPairI; E2S m_E2Subjects;};#endif // !defined(AFX_OBSERVER_H__8E1444BE_2F25_4EE1_AC1A_6C963256B129__INCLUDED_)
// Observer.cpp: implementation of the Observer class.////#include "stdafx.h"#include "Observer.h"#include "Subject.h"Observer::Observer(){}Observer::~Observer(){ ClearSubjects();}void Observer::ClearSubjects(){ E2S::iterator E2SI = m_E2Subjects.begin(); while(E2SI != m_E2Subjects.end()) { Subject* pSubject = (E2SI->second ); pSubject->DetachObserver(this); E2SI = m_E2Subjects.begin(); } m_E2Subjects.clear(); }void Observer::NotifySubject(unsigned int Event, const std::stringstream& Data){ ESPairI subjects = m_E2Subjects.equal_range(Event); ESI esi = subjects.first; while(esi != subjects.second) { Subject* pSubject = esi->second; pSubject->Notify(this,Event,Data); ++esi; } }void Observer::RemoveSubject(Subject* pSubject){ ESI esi = m_E2Subjects.begin(); for(; esi != m_E2Subjects.end();) { if(esi->second == pSubject) { esi = m_E2Subjects.erase(esi); } else { esi ++; } } }void Observer::Observe(Subject* pSubject, unsigned int Event){ m_E2Subjects.insert(ESPair(Event, pSubject)); pSubject->AttachObserver(this, Event); //m_bOnUpdating = false;}void Observer::UnObserve(Subject* pSubject, unsigned int Event){ pSubject->DetachObserver(this, Event);}
Subject和Observer都不是线程安全的,如果要支持多线程, Subject和Observer的函数都要加互斥体(Mutex)。
单元测试
这里使用Google Test作为单元测试的框架。(说明一下:以下的单元测试并没有对每个单独的类做单元测试,只是对整个框架做单元测试。)
使用一个测试装置(Test Fixture)来测试:声明一个Invoker类,维护一个元素为int型的list,并负责压入和弹出元素、清除list、显示list、“观察”Undo/Redo状态变化等工作。我们的测试将对这个list以及对其追加数据的命令(见下面MockCommandReceiver)而展开。
#pragma once#include <list>#include "UndoRedo\Observer.h"#include "gtest\gtest.h"class Command;class Invoker : public Observer, public ::testing::Test{public: Invoker(); ~Invoker(); void PushElement(int nElement); void PopElement(); void ClearAllElements(); void DisplayList() const; virtual bool Update(Subject* pSubject, Observer* pObserverFrom, unsigned int Event, const std::stringstream& Data);protected: Command * ConstructCommand(int nElement); void UpdateUndoRedoState(bool bUndoable, bool bRedoable);protected: std::list<int> m_listElements;};
#include "StdAfx.h"#include <iostream>#include "Invoker.h"#include "UndoRedo\CommandManager.h"#include "UndoRedo\Command.h"#include "UndoRedo\MacroCommand.h"#include "MockCommandReceiver.h"#include "MockCommand.h"Invoker::Invoker(){ Observe(CommandManager::Instance(), EVENT_UNDOREDOSTATECHANGED); UpdateUndoRedoState(CommandManager::Instance()->CanUndo(), CommandManager::Instance()->CanRedo());}Invoker::~Invoker(){ UnObserve(CommandManager::Instance(), EVENT_UNDOREDOSTATECHANGED);}void Invoker::UpdateUndoRedoState(bool bUndoable, bool bRedoable){ std::cout << "Undoable : " << (bUndoable?"True":"False") << "\n"; std::cout << "Redoable : " << (bRedoable?"True":"False") << "\n\n";}bool Invoker::Update(Subject* pSubject, Observer* pObserverFrom, unsigned int Event, const std::stringstream& Data){ std::stringstream data(Data.str().c_str()); if (Event == EVENT_UNDOREDOSTATECHANGED) { bool bUndoable; data >> bUndoable; char c; data >> c; bool bRedoable; data >> bRedoable; UpdateUndoRedoState(bUndoable, bRedoable); } return true;}Command * Invoker::ConstructCommand(int nElement){ MockCommandReceiver * pReceiver = (MockCommandReceiver *)CREATECOMMANDRECEIVER(MockCommandReceiver); Command * pCommand = (Command *)CREATECOMMAND(Command); pCommand->SetReceiver(pReceiver); pReceiver->PrepareData(this, nElement); return pCommand;}void Invoker::PushElement(int nElement){ m_listElements.push_back(nElement);}void Invoker::PopElement(){ m_listElements.pop_back();}void Invoker::ClearAllElements(){ m_listElements.clear();}void Invoker::DisplayList() const{ if (m_listElements.size() == 0) { std::cout << "List: <Empty>"; } else { std::cout << "List: "; int i = 0; std::list<int>::const_iterator iter; for (iter = m_listElements.begin(); i < 10 && iter != m_listElements.end() ; i++, iter++) { std::cout << "" << *iter; } if (iter != m_listElements.end()) { std::cout << " and other " << m_listElements.size()-10 << " elements..."; } } std::cout << "\n\n";}
声明MockCommandReceiver来实现对list追加数据的操作。MockCommandReceiver是BaseCommandReceiver 的子类。
#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++设计新思维》部分章节