您的位置:首页 > 其它

使用设计模式实现Undo,Redo框架

2008-12-10 13:38 771 查看
编辑器程序少不了要支持undo, redo功能,如何实现?本文就是参考了设计模式中给出的思路实现了一个。

这里主要用到了两个模式:命令(command)模式, 备忘录(memento)模式

所谓编辑,功能上可以分成3个原子操作:添加新内容,编辑已有内容,删除已有内容, 因此编辑功能3个command实现,AddCommand, EditCommand, DeleteCommand. 这3个命令实现了相同的接口do, undo.

先说一下代码的风格,自从使用WTL,就喜欢上了WTL的代码风格,本文就是仿照WTL风格写的.

先看一下Command的接口:

class CCommand

{

public:

virtual ~CCommand();

public:

virtual bool Do() = 0;

virtual bool Undo() = 0;

virtual bool CanUndo() = 0;

};

AddCommand, EditCommand, DeleteCommand都实现了这个接口类。

下面WTL风格代码开始了:

先留个空架子

template<class TBase/*where TBase : public CCommand*/>

class CCommandImplBase : public TBase

{

//先空着,以后留着扩展

};

下面是主要实现:用到了event, 当Command执行时会触发event,你可以在时间里做一些诸如试图更新, 所谓event,跟C#里的事件相似,感兴趣的可以看我前面的文章用C++模拟C#事件机制

template< class T, class TBase = CCommand, class TTraits = CommandTraits<T> >

class CCommandImpl : public CCommandImplBase<TBase>

{

public:

typedef TTraits::CmdEvent CmdEvent;

typedef CmdEvent::EventHandler EventHandler;

public:

CCommandImpl(typename CmdEvent::EventArgs/* const& */args) : m_Args(args)

{

#if (_MSC_VER >= 1300)

//COMPLIE_ASSERT(IsPointer(args));

bool bRet = TypeTraits<CmdEvent::EventArgs>::IsPointer();

#endif

}

virtual ~CCommandImpl()

{

}

public:

void RegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)

{

m_DoEvent += handler;

}

void UnRegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)

{

m_DoEvent -= handler;

}

void RegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& handler)

{

m_UndoEvent += handler;

}

void UnRegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& handler)

{

m_UndoEvent -= handler;

}

public:

virtual bool Do()

{

if (!DoCommand())

{

return false;

}

typename CmdEvent::ReturnValue ret = m_DoEvent(GetEventArgs());

return true;

}

virtual bool Undo()

{

if (!CanUndo())

{

return false;

}

if (!UndoCommand())

{

return false;

}

typename CmdEvent::ReturnValue ret = m_UndoEvent(GetEventArgs());

return true;

}

virtual bool CanUndo()

{

return TTraits::CanUndo;

}

protected:

virtual bool DoCommand() = 0;

virtual bool UndoCommand(){return true;}

// implementations

protected:

typename CmdEvent::EventArgs /*const&*/ GetEventArgs() const

{

return m_Args;

}

private:

CmdEvent m_DoEvent;

CmdEvent m_UndoEvent;

typename CmdEvent::EventArgs m_Args;

};

这里用到了CommandTraits模板,所谓Traits,能在编译时提供一些类型信息,感兴趣的同学可以去侯捷的网站看看Traits: 類型的else-if-then機製,相关的文章好多。

CommandTraits是一系列模板特化, 每个Traits包含一个event类型, 这在前面已经用到了.

template<class T>

struct CommandTraits;

//Common Command Traits

template<>

struct CommandTraits<CAddCommand>

{

typedef Event<bool, CAddEventArgs const*> CmdEvent;

enum { CanUndo = true};

};

template<>

struct CommandTraits<CEditCommand>

{

typedef Event<bool, CEditEventArgs const*> CmdEvent;

enum { CanUndo = true};

};

template<>

struct CommandTraits<CDeleteCommand>

{

typedef Event<bool, CDeleteEventArgs const*> CmdEvent;

enum { CanUndo = true};

};

在接下来就是 AddCommand, EditCommand, DeleteCommand 的实现了

可以使用备忘录模式将每个命令需要保存的数据(CmdEvent::EventArgs)提取出来,可以用一组get,set操作实现状态的提取和保存,具体保存的内容因项目而易,很简单,不罗嗦了:

class CAddCommand : public CCommandImpl<CAddCommand>

{

public:

CAddCommand(CAddEventArgs const* pArgs);

virtual ~CAddCommand();

protected:

virtual bool DoCommand()

{

//这里写实际的代码,比如可以将输入的内容存起来. 可以考虑用备忘录模式实现下同,就不罗列代码了

}

virtual bool UndoCommand()

{

//这里写实际的代码,比如可以将输入的内容删掉. 下同,就不罗列代码了

}

};

class CEditCommand : public CCommandImpl<CEditCommand>

{

public:

CEditCommand(CEditEventArgs const* pArgs);

virtual ~CEditCommand();

protected:

virtual bool DoCommand();

virtual bool UndoCommand();

};

class CDeleteCommand : public CCommandImpl<CDeleteCommand>

{

public:

CDeleteCommand(CDeleteEventArgs const* pArgs);

virtual ~CDeleteCommand();

protected:

virtual bool DoCommand();

virtual bool UndoCommand();

};

所有的命令都全了,可是还要把所有命令按顺序存储,Undo的时候这按这个相反的顺序拿出来就可以了,用std::stack正好,下面实现了一个CCommandManager , 就是用来管理命令的:

template< class TCommand /*where TCommand : public CCommand*/ >

class CCommandManager

{

public:

CCommandManager()

:m_UndoStack()

{

}

virtual ~CCommandManager(){}

public:

bool Excecute(TCommand* cmd)

{

if (!cmd->Do())

{

return false;

}

if (cmd->CanUndo())

{

m_UndoStack.push(cmd);

}

return true;

}

bool ReExecute()

{

TCommand* cmd = m_RedoStack.top();

if (!Excecute(cmd))

{

return false;

}

m_RedoStack.pop();

return true;

}

bool UnExecute()

{

TCommand* cmd = m_UndoStack.top();

if (!cmd->Undo())

{

return false;

}

m_UndoStack.pop();

m_RedoStack.push(cmd);

return true;

}

void Reset()

{

if (!m_UndoStack.empty())

{

TCommand* pCmd = m_UndoStack.top();

m_UndoStack.pop();

delete pCmd;

}

}

private:

std::stack<TCommand*> m_UndoStack;

std::stack<TCommand*> m_RedoStack;

};

CCommandManager<CCommand>还可以实现为单件模式,这样可以提供一个全局访问点,这里因为篇幅关系不实现了。

至此整个Undo,Redo的框架已经实现完了,下面介绍如何使用

//1. 定义命令参数并填写需要的值

CAddEventArgs* pArgs = new CAddEventArgs(......);

//2. 定义Command, 并把相应的参数填进去.

CAddCommand* cmd = new CAddCommand(pArgs);

//3. 定义事件响应函数.

CAddCommand::EventHandler Handler(GetView(), &CKeyinToolView::OnEditPOI);

//4. 把事件响应函数注册到event

cmd ->RegisterDoHandler(Handler);

//5. 现在放心的运行命令,运行成功则CommandManager会信息保存到Undo栈中,并触发event处理函数.

GetCommandManager()->Excecute(cmd)。

//6. 在需要Undo的时候只需这样

GetCommandManager()->UnExcecute()。

//7. 在需要Redo的时候只需这样

GetCommandManager()->ReExcecute()。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: