C++ 用Command模式简单的实现Undo&Redo功能

转自:https://www.cnblogs.com/TianFang/archive/2013/05/19/3086820.html
许多GUI程序中提供一个"撤销&重做"的功能,这个功能对用户来说非常友好;本文就简单的介绍一下如何用C#实现该功能。

实现Undo&Redo功能的基本模型是带撤销功能的命令模式,它将每步操作保存为一个命令对象,如下所示:

interface Icommand
{
    void Do();
    void Undo();
}

其中Do函数执行功能,Undo函数回滚功能。这样就把命令给实体化了,只要将命令对象给保存下来,需要撤销时执行Undo函数,重做时执行Do函数即可。

有了这个基本思路后,下面就是实现细节了:

  1. 申请两个Stack来保存命令对象:UndoStack和RedoStack
  2. 执行命令时,将命令序列化为Command对象,执行Do方法,存入UndoStack,清空RedoStack
  3. 撤销命令时,从UndoStack中取出命令,执行Undo方法,存入RedoStack
  4. 重做命令时,从RedoStack中取出命令,执行Do方法,存入UndoStack

简单的实现:

#ifndef _COMMAND_HPP
#define _COMMAND_HPP
 
#include <iostream>
#include <stack>
#include <string>
 
class Command
{
public:
	Command(){}
	virtual ~Command(){}
 
	virtual void Execute() = 0;
};
 
 
class InputCommand : public Command
{
	public:
 
		InputCommand( const std::string &input )
		{
			mInput = input;
		}
		~InputCommand()
		{}
 
		void Execute()
		{
			printf( "current string: %s\n", mInput.c_str() );
		}
 
private:
 
	std::string mInput;
};
 
class Commander
{
public:
	Commander( Command *pCmd )
	{
		//最初的命令数据
		mUndo.push( pCmd );
	}
	~Commander()
	{
		while( false == mUndo.empty() )
		{
			delete mUndo.top();
			mUndo.pop();
		}
		while( false == mRedo.empty() )
		{
			delete mRedo.top();
			mRedo.pop();
		}
	}
	
	void ExecuteCommand( Command *pCmd )
	{
		pCmd->Execute();
		mUndo.push( pCmd );
	}
 
	void Undo()
	{
		if( mUndo.size() < 2 )
		{
			//无法后退撤销没有数据
			printf( "undo failed.\n" );
			return;
		}
 
		printf( "undo:\n" );
 
		//当前命令
		auto pCmd = mUndo.top();
 
		//保存当前命令
		mRedo.push( pCmd );
 
		//弹出
		mUndo.pop();
 
		//还原到上个命令
		pCmd = mUndo.top();
		
		pCmd->Execute();
	}
 
	void Redo()
	{
		if( mRedo.empty() )
		{
			//无法前进重做没有数据
			printf( "redo failed.\n" );
			return;
		}
 
		printf( "redo:\n" );
 
		auto pCmd = mRedo.top();
 
		pCmd->Execute();
 
		mRedo.pop();
 
		mUndo.push( pCmd );
	}
 
private:
	std::stack< Command* > mUndo;
	std::stack< Command* > mRedo;
};
 
#endif

#include "Command.hpp"
 
//模拟输入字符串,然后对输入的字符串进行undo redo操作
//你可以打开有undo redo功能的文本编辑程序测试是否是这样
//这里记录的是完整记录,即:即便在undo 或 redo 过程中又发生数据改变
//也会记录,如果不想这样在undo 或者 redo 输入新字符串时 将redo清空即可
//即认为在历史记录中修改值被认为是最新的值,不需要再redo
int main()
{
	//默认没有输入字符串可以是空,这里为了演示赋值一个特殊的字符串
	Commander *p = new Commander( new InputCommand( "[empty]" ) );
 
	//输入1234
	p->ExecuteCommand( new InputCommand( "1234" ) );
	//输入567
	p->ExecuteCommand( new InputCommand( "567" ) );
	//输入xxx
	p->ExecuteCommand( new InputCommand( "xxx" ) );
 
	//执行一次undo 撤销到 567
	p->Undo();
	//执行一次undo 撤销到 1234
	p->Undo();
 
	//undo后中间输入新字符串 insert 覆盖 1234
	p->ExecuteCommand( new InputCommand( "insert" ) );
 
	//执行一次undo 撤销到 1234
	p->Undo();
 
	//执行一次undo 撤销到最初情况 [empty]
	p->Undo();
 
	//执行失败已经undo 到最原始情况
	p->Undo();
 
	//执行一次redo 重做到 1234
	p->Redo();
	//执行一次redo 重做到 insert
	p->Redo();
	//执行一次redo 重做到 567
	p->Redo();
 
	//redo后中间输入新字符串 insert again 覆盖 567
	p->ExecuteCommand( new InputCommand( "insert again" ) );
 
	//执行一次undo 撤销到567
	p->Undo();
 
	//执行一次redo 重做到 insert again
	p->Redo();
 
	//执行一次redo 重做到 xxx
	p->Redo();
 
	//执行失败已经redo 到最新情况
	p->Redo();
	delete p;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/QIJINGBO123/article/details/87793642