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

来源:互联网 发布:淘宝订单可以拆分吗 编辑:程序博客网 时间:2024/04/27 20:55

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

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

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

 

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

先看一下Command的接口:

 

  1. class CCommand  
  2. {
  3. public:
  4.  virtual ~CCommand();
  5. public:
  6.  virtual bool Do() = 0;
  7.  virtual bool Undo() = 0;
  8.  virtual bool CanUndo() = 0;
  9. };

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

下面WTL风格代码开始了:

先留个空架子

 

 

  1. template<class TBase/*where TBase : public CCommand*/>
  2. class CCommandImplBase : public TBase
  3. {
  4. //先空着,以后留着扩展
  5. };

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

 

 

  1. templateclass T, class TBase = CCommand, class TTraits = CommandTraits<T> >
  2. class CCommandImpl : public CCommandImplBase<TBase>
  3. {
  4. public:
  5.  typedef TTraits::CmdEvent CmdEvent;
  6.  typedef CmdEvent::EventHandler EventHandler;
  7. public:
  8.  CCommandImpl(typename CmdEvent::EventArgs/* const& */args) : m_Args(args)
  9.  {
  10. #if (_MSC_VER >= 1300)
  11.   //COMPLIE_ASSERT(IsPointer(args));
  12.   bool bRet = TypeTraits<CmdEvent::EventArgs>::IsPointer();
  13. #endif
  14.  }
  15.  virtual ~CCommandImpl()
  16.  {
  17.  }
  18. public:
  19.  void RegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)
  20.  {
  21.   m_DoEvent += handler;
  22.  }
  23.  void UnRegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)
  24.  {
  25.   m_DoEvent -= handler;
  26.  }
  27.  void RegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& handler)
  28.  {
  29.   m_UndoEvent += handler;
  30.  }
  31.  void UnRegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& handler)
  32.  {
  33.   m_UndoEvent -= handler;
  34.  }
  35. public:
  36.  virtual bool Do()
  37.  {
  38.   if (!DoCommand())
  39.   {
  40.    return false;
  41.   }
  42.   typename CmdEvent::ReturnValue ret = m_DoEvent(GetEventArgs());
  43.   
  44.   return true;
  45.  }
  46.  virtual bool Undo()
  47.  {
  48.   if (!CanUndo())
  49.   {
  50.    return false;
  51.   }
  52.   
  53.   if (!UndoCommand())
  54.   {
  55.    return false;
  56.   }
  57.   typename CmdEvent::ReturnValue ret = m_UndoEvent(GetEventArgs());
  58.   
  59.   return true;
  60.  }
  61.  virtual bool CanUndo()
  62.  {
  63.   return TTraits::CanUndo;
  64.  }
  65. protected:
  66.  virtual bool DoCommand() = 0;
  67.  virtual bool UndoCommand(){return true;}
  68. // implementations
  69. protected:
  70.  typename CmdEvent::EventArgs /*const&*/ GetEventArgs() const
  71.  {
  72.   return m_Args;
  73.  }
  74. private:
  75.  CmdEvent m_DoEvent;
  76.  CmdEvent m_UndoEvent;
  77.  typename CmdEvent::EventArgs m_Args;
  78. };

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

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

 

  1. template<class T>
  2. struct CommandTraits;
  3. //Common Command Traits
  4. template<>
  5. struct CommandTraits<CAddCommand>
  6. {
  7.  typedef Event<bool, CAddEventArgs const*> CmdEvent;
  8.  enum { CanUndo = true};
  9. };
  10. template<>
  11. struct CommandTraits<CEditCommand>
  12. {
  13.  typedef Event<bool, CEditEventArgs const*> CmdEvent;
  14.  enum { CanUndo = true};
  15. };
  16. template<>
  17. struct CommandTraits<CDeleteCommand>
  18. {
  19.  typedef Event<bool, CDeleteEventArgs const*> CmdEvent;
  20.  enum { CanUndo = true};
  21. };

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

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

 

 

  1. class CAddCommand : public CCommandImpl<CAddCommand>
  2. {
  3. public:
  4.  CAddCommand(CAddEventArgs const* pArgs);
  5.  virtual ~CAddCommand();
  6. protected:
  7.  virtual bool DoCommand()
  8.  {
  9.     //这里写实际的代码,比如可以将输入的内容存起来. 可以考虑用备忘录模式实现下同,就不罗列代码了
  10.  }
  11.  virtual bool UndoCommand()
  12.  {
  13.     //这里写实际的代码,比如可以将输入的内容删掉. 下同,就不罗列代码了
  14.  }
  15. };
  16. class CEditCommand : public CCommandImpl<CEditCommand>
  17. {
  18. public:
  19.  CEditCommand(CEditEventArgs const* pArgs);
  20.  virtual ~CEditCommand();
  21. protected:
  22.  virtual bool DoCommand();
  23.  virtual bool UndoCommand();
  24. };
  25. class CDeleteCommand : public CCommandImpl<CDeleteCommand>
  26. {
  27. public:
  28.  CDeleteCommand(CDeleteEventArgs const* pArgs);
  29.  virtual ~CDeleteCommand();
  30. protected:
  31.  virtual bool DoCommand();
  32.  virtual bool UndoCommand();
  33. };

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

 

 

  1. templateclass TCommand /*where TCommand : public CCommand*/ >
  2. class CCommandManager  
  3. {
  4. public:
  5.  CCommandManager()
  6.  :m_UndoStack()
  7.  {
  8.  }
  9.  virtual ~CCommandManager(){}
  10. public:
  11.  bool Excecute(TCommand* cmd)
  12.  {
  13.   if (!cmd->Do())
  14.   {
  15.    return false;
  16.   }
  17.   if (cmd->CanUndo())
  18.   {
  19.    m_UndoStack.push(cmd);
  20.   }
  21.   return true;
  22.  }
  23.  bool ReExecute()
  24.  {
  25.   TCommand* cmd = m_RedoStack.top();
  26.   
  27.   if (!Excecute(cmd))
  28.   {
  29.    return false;
  30.   }
  31.   m_RedoStack.pop();
  32.   return true;  
  33.  }
  34.  bool UnExecute()
  35.  {
  36.   TCommand* cmd = m_UndoStack.top();
  37.   
  38.   if (!cmd->Undo())
  39.   {
  40.    return false;
  41.   }
  42.   m_UndoStack.pop();
  43.   m_RedoStack.push(cmd);
  44.   return true;
  45.  }
  46.  void Reset()
  47.  {
  48.   if (!m_UndoStack.empty())
  49.   {
  50.    TCommand* pCmd = m_UndoStack.top();
  51.    m_UndoStack.pop();
  52.    delete pCmd;
  53.   }
  54.  }
  55. private:
  56.  std::stack<TCommand*> m_UndoStack;
  57.  std::stack<TCommand*> m_RedoStack; 
  58. };

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

 

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

  1. //1. 定义命令参数并填写需要的值
  2. CAddEventArgs* pArgs = new CAddEventArgs(......);
  3. //2. 定义Command, 并把相应的参数填进去.
  4. CAddCommand* cmd = new CAddCommand(pArgs);
  5. //3. 定义事件响应函数.
  6. CAddCommand::EventHandler Handler(GetView(), &CKeyinToolView::OnEditPOI);
  7. //4. 把事件响应函数注册到event
  8. cmd ->RegisterDoHandler(Handler);
  9. //5. 现在放心的运行命令,运行成功则CommandManager会信息保存到Undo栈中,并触发event处理函数.
  10. GetCommandManager()->Excecute(cmd)。
  11. //6. 在需要Undo的时候只需这样
  12. GetCommandManager()->UnExcecute()。
  13. //7. 在需要Redo的时候只需这样
  14. GetCommandManager()->ReExcecute()。

 

原创粉丝点击