目录
前言
框架设计
代码实现
单元测试
后记
参考资料
前言
终于结束赋闲在家的状态,又走上研发经理的岗位。老板“教导”我说:“作为‘空降’的管理者,要想得到团队中其他成员的信任和认可,必须身先士卒,去解决开发中难题。”言下之意很明显,得先干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状态改变的时序图依次如下:
代码实现
BaseCommandManager.h#pragma once class 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; };
BaseCommand.h#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的代码见下)
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的子类。
CommandManager.h#pragma once #include <stack> #include "BaseCommandManager.h" #include "Subject.h" #include "Singleton.h" #define EVENT_UNDOREDOSTATECHANGED 1 class 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#include "StdAfx.h" #include "CommandManager.h" #include "BaseCommand.h" CommandManager::UndoRedoStateInspector::UndoRedoStateInspector(CommandManager * pCommandManager): m_pCommandManager(pCommandManager), m_bUndoable(pCommandManager->CanUndo()), m_bRedoable(pCommandManager->CanRedo()) { } CommandManager::UndoRedoStateInspector::~UndoRedoStateInspector() { if (m_bUndoable != m_pCommandManager->CanUndo() || m_bRedoable != m_pCommandManager->CanRedo()) { std::stringstream ssData; ssData << m_pCommandManager->CanUndo(); ssData << ','; ssData << m_pCommandManager->CanRedo(); m_pCommandManager->Notify(NULL, EVENT_UNDOREDOSTATECHANGED, ssData); } } CommandManager::CommandManager() { } CommandManager::~CommandManager() { ClearAllCommands(); } CommandManager * CommandManager::Instance() { return Singleton<CommandManager>::Instance(); } bool CommandManager::CallCommand(BaseCommand * pCommand) { UndoRedoStateInspector si(this); if (pCommand) { if (pCommand->Execute()) { PushUndoCommand(pCommand); DeleteRedoCommands(); return true; } else { delete pCommand; } } return false; } void CommandManager::ClearAllCommands() { UndoRedoStateInspector si(this); DeleteUndoCommands(); DeleteRedoCommands(); } void CommandManager::Undo() { UndoRedoStateInspector si(this); BaseCommand * pCommand = PopUndoCommand(); if (pCommand) { if (pCommand->Unexecute()) { PushRedoCommand(pCommand); } else { delete pCommand; } } } void CommandManager::Redo() { UndoRedoStateInspector si(this); BaseCommand * pCommand = PopRedoCommand(); if (pCommand) { if (pCommand->Execute()) { PushUndoCommand(pCommand); } else { delete pCommand; } } } bool CommandManager::CanUndo() const { return !m_stackUndo.empty(); } bool CommandManager::CanRedo() const { return !m_stackRedo.empty(); } void CommandManager::PushUndoCommand(BaseCommand * pCommand) { if (pCommand) { m_stackUndo.push(pCommand); } } BaseCommand * CommandManager::PopUndoCommand() { BaseCommand * pCommand = NULL; if (!m_stackUndo.empty()) { pCommand = m_stackUndo.top(); m_stackUndo.pop(); } return pCommand; } void CommandManager::PushRedoCommand(BaseCommand * pCommand) { if (pCommand) { m_stackRedo.push(pCommand); } } BaseCommand * CommandManager::PopRedoCommand() { BaseCommand * pCommand = NULL; if (!m_stackRedo.empty()) { pCommand = m_stackRedo.top(); m_stackRedo.pop(); } return pCommand; } void CommandManager::DeleteUndoCommands() { while (!m_stackUndo.empty()) { delete m_stackUndo.top(); m_stackUndo.pop(); } } void CommandManager::DeleteRedoCommands() { while (!m_stackRedo.empty()) { delete m_stackRedo.top(); m_stackRedo.pop(); } }
CommandManager实现为单件(这不是必须的,只不过我们的系统需要这样做)。这个单件由Singleton模板类实现(Singleton<CommandManager>::Instance())。顺便说一句,把Singleton做成模板类的好处是:单件有许多变种(Mayers单件、Phoenix单件、带寿命的单件和双检测锁定单件等),当需要修改单件的实现方法时,只需改这个模板类即可,不用每个单件类都去修改。Singleton模板类代码如下(这里Singleton不是线程安全的,需要加双检测锁定才能支持多线程):
Singleton.h#pragma once template <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等,目的是方便调用(可以使调用者少敲一些字符)。
Command.h#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; };
Command.cpp#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);
MacroCommand.h#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; };
MacroCommand.cpp#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)); } }
Util.h#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容器)。
BaseCommandReceiver.h#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// 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// 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// 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// 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)而展开。
Invoker.h#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; };
Invoker.cpp#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 的子类。
MockCommandReceiver.h#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; };
MockCommandReceiver.cpp#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来模拟执行成功和失败。
MockCommand.h#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; };
MockCommand.cpp#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)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》