目录
前言
框架设计
代码实现
单元测试
后记
参考资料
前言
终于结束赋闲在家的状态,又走上研发经理的岗位。老板“教导”我说:“作为‘空降’的管理者,要想得到团队中其他成员的信任和认可,必须身先士卒,去解决开发中难题。”言下之意很明显,得先干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》