命令模式实现 Undo Redo

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)设计模式。见下面的类图:

 

clip_image002

 

如果用C#,可能用接口(interface)来定义它们比较好,比如定义ICommandICommandManager。但C++中没有interface,所以用抽象类(Abstract Class)来实现,所有方法都声明为纯虚函数。

 

至于Command设计模式,无需多说,无非这里的Command 模式带Undo/Redo功能。

 

二、下一步当然是BaseCommandManager的实现子类CommandManager,见下面的类图:

 

clip_image004

 

CommandManager内部会维护着2个栈(Undo StackRedo Stack),并增加相应的操作栈的私有方法,如:PushUndoCommandPopUndoCommand等。CommandManager基于这个数据结构来实现BaseCommandManager声明的所有纯虚函数。

 

三、下面是BaseCommand的实现子类Command。根据《Head First设计模式》里说得,这里有两种方案,一种是“傻瓜式”的Command,即Command持有一个接收者(Receiver)的指针,所有具体的命令都由Receiver来处理,Command对如何处理命令一无所知,像“傻瓜”一样。另一种是“聪明”的CommandBaseCommand的子类知道如何处理命令并直接处理。这里我采用了“傻瓜式”的Command(当然此框架也支持“聪明”的Command,但Command子类需要根据客户程序的需要由框架的使用者自己来实现了),见下面类图:

 

clip_image006

 

BaseCommandReceiver也是个抽象类,声明一个纯虚函数ActionCommandExecuteUnexecute方法分别用falsetrue作为参数调用虚方法Action以执行命令。

 

客户程序在生成Command的同时,应该给其赋予一个BaseCommandReceiver的指针m_pReceiver。缺省地,Command销毁的时候会一并销毁m_pReceiver,但客户程序也可通过设置bAutoDeletefalse,来自己销毁。

 

BaseCommandReceiver的子类负责实际的命令处理。

 

四、很多书中介绍Command设计模式的时候,都会提到组合命令。用组合命令可以实现命令的“批处理”。我们这里也需要,所以要实现BaseCommand的另一个子类MacroCommand。见下面类图:

 

clip_image008

 

MacroCommand维护一个Command的集合(这里用std::vector实现),客户程序可以添加命令(AddCommand)和删除命令(DeleteCommand)。当然MacroCommand也要实现BaseCommandExecuteUnexecute函数,具体的实现就是遍历vector中的Command,逐个执行和逐个撤销。

 

五、支持Undo/Redo的应用程序,一般都有“Undo”和“Redo”两个按钮,那么当命令的Undo/Redo栈内容变化的时候,两个按钮会根据是否“可撤销”和“可恢复”相应地变化为EnabledDisabled状态。那么框架支持这个功能是通过观察者(Observer)设计模式来完成的。Observer模式也无需多说,见下面的类图:

 

clip_image010

 

简单的说,当CommandManagerUndoRedo栈由空变为不空时,或由不空变为空时,都会调用SubjectNotify函数通知所有观察者,来更新UI(比如:Undo/Redo按钮的Enabled/Disabled状态)。

 

六、这个框架支持的五个基本操作:执行命令、UndoRedo、清除Undo/Redo历史记录、Undo/Redo状态改变的时序图依次如下:

 

clip_image012 clip_image014 clip_image016 clip_image018clip_image020

 

代码实现

 

#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,它的作用相当于一个“门卫”,“守卫”在CommandManagerCallCommandClearAllCommandsUndoRedo函数的“门口”,它可以在进入函数时(即构造UndoRedoStateInspector时)保存CanUndoCanRedo的状态,当退出函数时(即析构UndoRedoStateInspector时),检查2个状态是否改变,如改变则通知观察者(Observer)们状态已改变。这个做法非常类似于多线程编程中经常使用的Lock对象(即在构造时获得Mutex,析构时释放Mutex)。

 

CommandManager的最后还定义了几个宏CALLCOMMANDUNDOREDO等,目的是方便调用(可以使调用者少敲一些字符)。

 

#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的子类。

 

最后是观察者设计模式(SubjectObserver)的代码,见下:

 

// 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);}

 

SubjectObserver都不是线程安全的,如果要支持多线程, SubjectObserver的函数都要加互斥体(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追加数据的操作。MockCommandReceiverBaseCommandReceiver 的子类。

 

#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设计模式》封装调用:命令模式

《敏捷软件开发 - 原则、模式与实践(C#版)》第21 COMMAND模式

C++设计新思维》部分章节

Getting started with Google C++ Testing Framework

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值