Undo/Redo框架(C++,带源码)

 

目录

 

前言

框架设计

代码实现

单元测试

后记

参考资料

 

前言

 

终于结束赋闲在家的状态,又走上研发经理的岗位。老板“教导”我说:“作为‘空降’的管理者,要想得到团队中其他成员的信任和认可,必须身先士卒,去解决开发中难题。”言下之意很明显,得先干Hands-on的工作。于是我便有了做现有系统图形操作的撤销和恢复(Undo/Redo)功能的任务,因为这项工作被其他人认为是比较难啃的骨头(原因是你要在现有功能的实现代码中加入这个 Undo/Redo,而这些代码是由多人写的,要读懂它们就得费不少功夫,最多的一个操作2000多行代码,还不算间接调用的函数)。

 

当然,实现具体功能的Undo/Redo之前,首先要搭建一套Undo/Redo的框架。不过好在实现Undo/Redo框架还不是那么复杂。以前看到过一些关于如何实现Undo/Redo功能的书和网页,带过的团队也曾做过Undo/Redo,但是自己亲自下手,还是头一回。下面就把设计、实现和测试的过程回顾一下,算是做个总结。

 

框架设计

 

一、最基本的,当然是使用命令(Command)设计模式。见下面的类图:

 

Undo/Redo框架(C++,带源码)_第1张图片

 

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

 

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

 

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

 

Undo/Redo框架(C++,带源码)_第2张图片

 

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

 

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

 

Undo/Redo框架(C++,带源码)_第3张图片

 

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

 

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

 

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

 

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

 

Undo/Redo框架(C++,带源码)_第4张图片

 

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

 

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

 

Undo/Redo框架(C++,带源码)_第5张图片

 

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

 

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

 

Undo/Redo框架(C++,带源码)_第6张图片 Undo/Redo框架(C++,带源码)_第7张图片 Undo/Redo框架(C++,带源码)_第8张图片 Undo/Redo框架(C++,带源码)_第9张图片Undo/Redo框架(C++,带源码)_第10张图片

 

代码实现

 

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 Factoryconst std::string>::instance()->CreateObject(strCommand);
    }
};

template <class DerivedCommand> 
class RegisterCommandClass
{
public:
    static BaseCommand * Create()
    {
        return new DerivedCommand;
    }
    RegisterCommandClass(const std::string& strId)
    {
        Factoryconst 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 
#include 
#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 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 AssocMap;
    AssocMap associations_;
};

template <class AbstractProduct,class Product> 
class RegisterClassToFactory
{
public:
    static AbstractProduct * Create()
    {
        return new Product;
    }

    RegisterClassToFactory(const std::string& id)
    {
        Factorystring>::instance()->Register(id, RegisterClassToFactory::Create);
    }
};

#define ClassNameToString(x) #x

 

所以在BaseCommand中定义RegisterCommandClass模板类来支持BaseCommand子类向工厂的注册。并且在BaseCommand类里增加CreateCommand静态函数(包括宏定义CREATECOMMAND),通过工厂在运行时可以“动态”生成BaseCommand的子类。

 

CommandManager.h#pragma once

#include 
#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;

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

 

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

 

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 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 RegisterCommandClass(ClassNameToString(Command));

 

在客户程序创建Command对象时,代码可能像下面这个样子:

 

Command * pCommand = (Command *)CREATECOMMAND(Command);

 

MacroCommand.h#pragma once

#include 
#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 m_vecCommands;
};

MacroCommand.cpp#include "StdAfx.h"
#include 
#include "MacroCommand.h"
#include "Util.h"

RegisterCommandClass RegisterCommandClass(ClassNameToString(MacroCommand));

MacroCommand::MacroCommand()
{
}

MacroCommand::~MacroCommand()
{
    ContainerDeleter>(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 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 Factoryconst std::string>::instance()->CreateObject(strCommandReceiver);
    }
};

template <class DerivedCommandReceiver> 
class RegisterCommandReceiverClass
{
public:
    static BaseCommandReceiver * Create()
    {
        return new DerivedCommandReceiver;
    }
    RegisterCommandReceiverClass(const std::string& strId)
    {
        Factoryconst 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// 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 
#include 

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 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 
#include 

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

 

SubjectObserver都不是线程安全的,如果要支持多线程, SubjectObserver的函数都要加互斥体(Mutex)。

 

单元测试

 

这里使用Google Test作为单元测试的框架。(说明一下:以下的单元测试并没有对每个单独的类做单元测试,只是对整个框架做单元测试。)

 

使用一个测试装置(Test Fixture)来测试:声明一个Invoker类,维护一个元素为int型的list,并负责压入和弹出元素、清除list、显示list、“观察”Undo/Redo状态变化等工作。我们的测试将对这个list以及对其追加数据的命令(见下面MockCommandReceiver)而展开。

 

Invoker.h#pragma once

#include 
#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 
#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: ";
    }
    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 的子类。

 

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 
#include "MockCommandReceiver.h"
#include "Invoker.h"

RegisterCommandReceiverClass 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 
#include "MockCommand.h"

RegisterCommandClass 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

 

转载于:https://www.cnblogs.com/wanghui9072229/archive/2011/08/29/2158960.html

你可能感兴趣的:(Undo/Redo框架(C++,带源码))