| 作者: | 熊春雷 |
| 网站: | http://www.autodev.net |
| Blog: | http://blog.csdn.net/pandaxcl |
| EMail: | pandaxcl@163.com |
| QQ: | 56637059 |
| 版本: | 0.01 于2007/09/25 |
| 目标: | 所有C++爱好者 |
| 版权: | 本文的版权归熊春雷所有 |
Warning
本文由熊春雷所写,绝对保证原创,在此特别严肃声明。
绝对不能容忍他人说本文为他所写以及其他的侵权行为。一旦发现,一定 尽本人最大的能力以法律的形式严追到底,决不妥协。
引用本文,要保证本文的完整性,不可以删除此处的声明,并且务必注明出处。
Tip
本文编写的所有代码可以用于任何用途(包括商业用途)。
用于商业用途的需要在最后发布的软件中声明借鉴了本文的思想。具体事 宜可以协商解决,(代码决不收取任何费用)。
其他事项可以和我联系,包括技术讨论等等:)或者直接登陆网站论坛: http://www.autodev.net
Note
本文受到了《C++设计新思维》和《产生式编程》两本书的影响,同时也查阅了大 量的资料,从Loki库和Boost库中也吸收了不少营养,特此感谢之。
本文由于处于原创阶段,难免会出现各种各样的错误。代码出现错误的可能性非常 小(本来想说为零的),因为文档和代码是严格同步的,这是由VST文本的include 所保证的,代码都是测试成功之后才发布的。
本文所编写的代码,经过了VC2005编译器和g++编译器的测试,并且都通过了。
本文还没有彻底完成,算是一个初级版本,未来还将继续完善。暂时发布出来是为 了预知读者群有多少,读者越多,我的成就感越强,写作的时候也会更有动力:)
本文还会继续完善,欢迎各位读者的批评指正,也接受各种各样的建议,在权衡之 后以决定是否加入本书。
本书还没有最终完成,还会不断的进行完善,更新之后的内容将会发表于我的 网站或我的博客。所以还需要读者多多关心本文的进展:)
Warning
本文的有效篇幅仅仅局限于基础篇,之后的文档还仅仅只是一种创意记录,
不保证正确性,特此提醒。
Contents
有了前面的这些基础性的代码之后,我们就可以处理大量的撤销和重做操作了:)但是通 常的情况下,我们还是希望能够往command里面添加一些额外的信息。
例如:为了在图形用户界面应用程序里面显示还可以撤销的命令队列列表以及可以重做的 命令列表,这两种情况下都需要给每一个命令添加一个额外的名称信息,所以我们将前面 的command代码修改为下面的basic_command代码:
class DefaultExtraData{};// 默认的额外信息
template<class ExtraData=DefaultExtraData>
struct basic_command :public ExtraData // 扩展了command类
{
virtual ~basic_command(){}// 避免内存泄漏
virtual void redo()=0;// 重做操作,兼任执行操作
virtual void undo()=0;// 撤销操作
};
template<> struct basic_command<DefaultExtraData>
{
virtual ~basic_command(){}// 避免内存泄漏
virtual void redo()=0;// 重做操作,兼任执行操作
virtual void undo()=0;// 撤销操作
};
特别注意上面的代码,为了避免空基类,采用了模板特化的静态选择机制 ,这样就可以根据模板参数选择合适的command类。
从上面的代码还可以看出,提供一个ExtraData给basic_command就可以完全的改变前面提 供的command的结构!可以增加属性,也可以增加方法。总之所有可以添加的信息都可以, 现在给出一个示例:
struct MyExtraData
{
std::string name;// 命令的名字
};
有了这个额外的数据之后,就可以采用这一系列的basic_nx模板类来使得命令类可以添加 额外的属性信息了;)当然仅仅限于basic_command模板类及其派生类才有这个功能, command仍然没有这个功能!
除了上面的给命令添加额外的信息需求之外,还有一个比较常见的问题会在应用 中出现:容器类中盛放的对象类型的默认构造函数不能调用的时候,前面的 container类就无能为力了!所以在此也要给出一种处理这种情况的能力!除此之外,前面 还假定:该容器类里面的所有对象的修改都是通过对象类的赋值运算符实现的, 这在大多数情况下是可以接受的,但是这样毕竟还是一个限制,而且这是不必要的!
可以处理非默认构造函数创建对象的容器类:
template<class T,class Config=basic_config<T> > class basic_container :public std::set<T*>
{
public:
typedef T object_type;
typedef std::list<T> objects_type;
typedef std::map<T*,int> unused_type;
typedef typename Config::create_type create_type;
typedef typename Config::modify_type modify_type;
private:
objects_type _objects;// 所有的对象都保存在这里
unused_type _unused;// 第二个参数是对应的变量被命令引用的数量
public:
// 得到标识号的命令引用计数
typename unused_type::mapped_type used(T**pID)
{
return _unused.find(*pID)==_unused.end()?0:_unused[*pID];
}
// 下面两个函数是在命令的创建和删除的时候调用的
// 配合_unused就是一个受管理的引用计数智能指针
// 只是引用该对象的“智能指针”只能是命令
void increase(T*ID){ _unused[ID]++; }
void decrease(T*ID){ _unused[ID]--; }
// 获得一个可以使用的对象空间,当没有对象可以回收的时候就创建一个
T*generate(T* used = NULL)
{
T* ptr = used;
typename unused_type::iterator it;
// 如果_unused中有引用计数为零的对象,直接返回该指针
for( it = _unused.begin(); it != _unused.end(); ++it )
{
if(0 == it->second && used != it->first)
{
ptr = it->first;
break;
}
}
// 如果_unused中没有引用计数为零的对象,在_objects中新建一个对象
// 并返回该新建对象的指针
if( used == ptr )
{
// Create避免了某些对象不可以通过默认构造函数创建的问题
_objects.push_back(create_type()());// 调用Create仿函数创建对象
ptr = &_objects.back();
}
return ptr;
}
public:
void create(T*ID)
{
this->insert(ID);
}
void modify(T*ID,const T&v)
{
modify_type()(*ID,v);// 通过指定的修改仿函数实现修改过程
}
void remove(T*ID)
{
this->erase(ID);
}
};
容器类增加了一个配置(basic_config)参数,配置参数中主要是创建仿函数和修改仿函数 !分别解决下面的问题:
创建仿函数(create)。主要用来处理那些默认构造函数不是public属性的类 ,创建这些对象就只能通过其他的构造函数了!
修改仿函数(modify)。主要用来处理那些不可以通过赋值运算符修改对象属 性的类,修改这些对象就只能通过其他的方法了!
有了上面的两个模板参数之后就可以使得basic_container类具有极大的可扩展性和处理能 力。下面是容器类需要的配置类:
template<class T>class basic_config
{
protected:
struct Create
{
const T&operator()()
{
static const T O;// 默认构造函数
return O;
}
};
struct Modify
{
void operator()(T&lhs,const T&rhs)
{
lhs = rhs;// 默认采用赋值运算符实现修改
}
};
public:
typedef Create create_type;
typedef Modify modify_type;
};
有了上面的这种容器类之后就可以处理创建命令特别常用的一种功能:创建对象的时候能 够提供创建参数,在创建对象的时候就对对象进行修改,只是这种修改是不可以撤销的! 可以添加额外信息并且可以提供创建参数的创建命令:
template<class Container,class Command> class basic_create :public Command
{
void initialize()
{
// 下面的generate函数主要是在第一次执行创建命令的时候起作用
_ID = _C.generate(_ID);// 如果_ID已经被使用了,则创建新的对象空间
_C.increase(_ID);// 容器类的引用计数增一
}
typedef typename Container::object_type T;
typedef typename Container::modify_type M;
public:
basic_create(Container&C,T*&ID):_C(C),_ID(ID)
{
initialize();
}
// 经常的时候还需要在创建的时候就赋予初始值
basic_create(Container&C,T*&ID,const T&O):_C(C),_ID(ID)//,_O(O)
{
initialize(); M()(*_ID , O);// 创建的时候直接赋初始值
}
virtual~basic_create()
{
_C.decrease(_ID);// 容器类的引用计数减一
}
void redo()
{
_C.create(_ID);
}
void undo()
{
_C.remove(_ID);
}
private:
basic_create(){}
Container &_C;
T*& _ID;
};
可以添加额外信息的修改命令:
template<class Container,class Command> class basic_modify :public Command
{
typedef typename Container::object_type T;
typedef typename Container::modify_type M;
public:
basic_modify(Container&C,T*ID,const T&O):_C(C),_ID(ID),_O(O)
{
M()(_OB , *_ID);// 备份信息
_C.increase(_ID);
}
virtual~basic_modify()
{
_C.decrease(_ID);
}
void redo()
{
_C.modify(_ID,_O);
}
void undo()
{
_C.modify(_ID,_OB);
}
private:
basic_modify(){}
Container &_C;
T*_ID;
T _O ;// 修改参数
T _OB;// 修改之前的对象备份
};
可以添加额外信息的删除命令:
template<class Container,class Command> class basic_remove :public Command
{
typedef typename Container::object_type T;
public:
basic_remove(Container&C,T*&ID):_C(C),_ID(ID),_IDB(ID)
{
_C.increase(_ID);
}
virtual~basic_remove()
{
_C.decrease(_ID);
}
void redo()
{
_C.remove(_ID);
_ID = NULL;// 标识号为NULL表示已经被删除了
}
void undo()
{
_ID = _IDB;// 撤销了删除操作,标识号应该复原
_C.create(_ID);
}
private:
basic_remove(){}
Container &_C ;
T*&_ID;
T* _IDB;
};
下面是三个基本命令的扩展代码的测试用例:
typedef undo::basic_container<std::string> CT;
typedef undo::basic_command<MyExtraData> Command;
typedef undo::basic_create<CT,Command> Create;
typedef undo::basic_modify<CT,Command> Modify;
typedef undo::basic_remove<CT,Command> Remove;
CT c;
CT::object_type* PANDAXCL = NULL;
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),0);
Create createPandaxcl(c,PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_EQUAL(c.size(),0);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),1);
// 在执行其它的命令之前必须保证这个对象已经存在
AUTOCXX_ASSERT_NOT_EQUAL(PANDAXCL,NULL);
createPandaxcl.redo();
AUTOCXX_ASSERT_NOT_EQUAL(PANDAXCL,NULL);
// 如果上面的命令不执行的话,后面的对这个对象的修改和移除操作都会失败
// 调用了上面的create命令后可以进行检查了
AUTOCXX_ASSERT_EQUAL(c.size(),1);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),1);
// 尝试create命令的撤销
createPandaxcl.undo();// 调用create命令的撤销操作
AUTOCXX_ASSERT_NOT_EQUAL(PANDAXCL,NULL);
// 调用了上面的create命令的撤销命令之后可以进行检查了
AUTOCXX_ASSERT_EQUAL(c.size(),0);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),1);
// 尝试create命令的重做操作
createPandaxcl.redo();// 调用create命令的重做操作
// 调用了上面的create命令的撤销命令之后可以进行检查了
AUTOCXX_ASSERT_EQUAL(c.size(),1);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),1);
// 现在来尝试一下modify命令,特别注意:使用modify和remove命令的时候
// 必须保证相应的对象已经存在了,否则结果就是未知的:)
// 下面的这个修改命令将ID为PANDAXCL的对象修改为大写形式
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),1);
Modify modifyPandaxcl(c,PANDAXCL,std::string("PANDAXCL"));
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),2);
// 调用modify的执行操作(又称重做操作)
modifyPandaxcl.redo();// 相当于执行操作
// 调用了上面的modify命令的执行命令之后可以进行检查了
AUTOCXX_ASSERT_EQUAL(c.size(),1);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),2);
AUTOCXX_ASSERT_NOT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("PANDAXCL"));
// 调用modify的撤销操作
modifyPandaxcl.undo();
// 调用了上面的modify命令的执行命令之后可以进行检查了
AUTOCXX_ASSERT_EQUAL(c.size(),1);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),2);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_NOT_EQUAL(*PANDAXCL,std::string("PANDAXCL"));
// 调用modify的重做操作(又称执行操作)
modifyPandaxcl.redo();
// 调用了上面的modify命令的重做命令之后可以进行检查了
AUTOCXX_ASSERT_EQUAL(c.size(),1);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),2);
AUTOCXX_ASSERT_NOT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("PANDAXCL"));
// 再次调用modify的撤销操作
modifyPandaxcl.undo();
// 调用了上面的modify命令的执行命令之后可以进行检查了
AUTOCXX_ASSERT_EQUAL(c.size(),1);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),2);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_NOT_EQUAL(*PANDAXCL,std::string("PANDAXCL"));
// 下面来尝试一下remove操作,同modify操作一样,执行remove操作的时候必须
// 保证指定ID的对象已经存在了!
// 下面的这个remove删除ID为PANDAXCL的对象
Remove removePandaxcl(c,PANDAXCL);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),3);
// 调用remove命令的执行操作
removePandaxcl.redo();
// 调用remove命令的执行操作之后来核实一下我们想要的结果
AUTOCXX_ASSERT_EQUAL(c.size(),0);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),0);
// 调用remove命令的撤销操作
removePandaxcl.undo();
// 调用remove命令的撤销操作之后来核实一下我们想要的结果
AUTOCXX_ASSERT_EQUAL(c.size(),1);
AUTOCXX_ASSERT_EQUAL(c.used(&PANDAXCL),3);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_NOT_EQUAL(*PANDAXCL,std::string("PANDAXCL"));
// 同样你也可以对一个命令执行很多次,但是需要特别注意上面的几个顺序问题
// 1. create命令可以在对象不存在的时候创建,但是不能创建相同ID的对象两
// 次及其以上
// 2. modify命令必须在指定ID存在的时候进行操作,否则程序运行意义未知
// 3. remove命令必须在指定ID存在的时候进行操作,否则程序运行意义未知
// 为了对上面的三种情况进行保证,才出现了undo里面的其它的几个模版类
可以添加额外信息的复合命令:
template<class Command>class basic_batch // 复合命令
:public Command // 是一种命令
,public std::list<Command*> // 用来记录子命令的命令队列
{
typedef Command command_type;
public:
virtual~basic_batch()// 当复合命令被释放的时候,所有的子命令也要释放
{
this->free();
}
void record(command_type*pCmd)// 记录子命令,子命令必须通过new创建
{
this->push_back(pCmd);
}
public:
virtual void redo()// 执行操作和重做操作
{
std::for_each(this->begin(),this->end(),std::mem_fun(&command_type::redo));
}
virtual void undo()// 撤销操作
{
std::for_each(this->rbegin(),this->rend(),std::mem_fun(&command_type::undo));
}
void free()// 用复合命令实现命令中心的时候,需要这个函数
{
std::for_each(this->begin(),this->end(),kill());// 释放子命令对象
this->clear();// 释放子命令对象指针
}
};
下面是复合命令的扩展代码的测试用例:
typedef undo::basic_command<MyExtraData> Command;
typedef undo::basic_container<std::string> CT;
typedef undo::basic_create<CT,Command> Create;
typedef undo::basic_modify<CT,Command> Modify;
typedef undo::basic_remove<CT,Command> Remove;
CT c;
CT::object_type* PANDAXCL = NULL;
CT::object_type* QQ = NULL;
CT::object_type* BLOG = NULL;
CT::object_type* EMAIL = NULL;
CT::object_type* NETWORK = NULL;
// 这里进行batch命令的测试
undo::basic_batch<Command> bat;
// 首先让这个batch类记录多个(5个)命令,可以是三个基本的命令,也可以是batch命令
// 特别注意这里的命令都必须是通过new操作符得到的对象,否则结果未知
bat.record(new Create(c,PANDAXCL));
bat.record(new Create(c,QQ));
bat.record(new Create(c,BLOG));
bat.record(new Create(c,EMAIL));
bat.record(new Create(c,NETWORK));
// 先检查初始状态
AUTOCXX_ASSERT_EQUAL(c.size(),0);
AUTOCXX_ASSERT_NOT_EQUAL(PANDAXCL,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(QQ ,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(BLOG ,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(EMAIL ,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(NETWORK ,NULL);
// 执行这个batch命令的执行操作,具体意义视其记录的命令而定
bat.redo();// 执行操作
AUTOCXX_ASSERT_NOT_EQUAL(PANDAXCL,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(QQ ,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(BLOG ,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(EMAIL ,NULL);
AUTOCXX_ASSERT_NOT_EQUAL(NETWORK ,NULL);
////////////////////////////////////////////////////////////////////////////////
// 为了测试的方便,在此从外部修改对象状态
*PANDAXCL = std::string("熊春雷");
*QQ = std::string("56637059");
*BLOG = std::string("http://blog.csdn.net/pandaxcl");
*EMAIL = std::string("pandaxcl@163.com");
*NETWORK = std::string("http://www.autodev.net");
////////////////////////////////////////////////////////////////////////////////
// 检查状态
AUTOCXX_ASSERT_EQUAL(c.size(),5);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_EQUAL(*QQ ,std::string("56637059"));
AUTOCXX_ASSERT_EQUAL(*BLOG ,std::string("http://blog.csdn.net/pandaxcl"));
AUTOCXX_ASSERT_EQUAL(*EMAIL ,std::string("pandaxcl@163.com"));
AUTOCXX_ASSERT_EQUAL(*NETWORK ,std::string("http://www.autodev.net"));
// 执行这个batch命令的撤销操作,具体意义视其记录的命令而定
bat.undo();
// 检查状态
AUTOCXX_ASSERT_EQUAL(c.size(),0);
// 执行这个batch命令的重做操作,具体意义视其记录的命令而定
bat.redo();// 表示重做操作而不是执行操作
// 检查状态
AUTOCXX_ASSERT_EQUAL(c.size(),5);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_EQUAL(*QQ ,std::string("56637059"));
AUTOCXX_ASSERT_EQUAL(*BLOG ,std::string("http://blog.csdn.net/pandaxcl"));
AUTOCXX_ASSERT_EQUAL(*EMAIL ,std::string("pandaxcl@163.com"));
AUTOCXX_ASSERT_EQUAL(*NETWORK ,std::string("http://www.autodev.net"));
// 其实batch命令里面可以使用create,modify,remove和batch一共4种子命令
// 为了测试,先把bat这个batch命令里面记录的所有子命令删除
bat.free();
// 重新记录新的命令
bat.record(new Modify(c,PANDAXCL,std::string("PANDAXCL")));
bat.record(new Remove(c,QQ));
// 为了在batch里面记录子batch命令,需要额外声明一个变量
// 假想第一次创建了一个错误的电子邮箱地址然后再进行修改的过程
undo::basic_batch<Command> * childbat = new undo::basic_batch<Command>();
childbat->record(new Modify(c,NETWORK,std::string("http://www.autocxx.net")));
childbat->record(new Modify(c,NETWORK,std::string("http://www.autodev.net")));
// 最后在父batch命令里面记录这个子batch命令,注意:这个子batch命令也是new出来的
bat.record(childbat);
// 执行这个重新记录的batch命令的执行操作,具体意义视其记录的命令而定
bat.redo();// true表示执行操作
// 检查状态
AUTOCXX_ASSERT_EQUAL(c.size(),4);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("PANDAXCL"));
//AUTOCXX_ASSERT_EQUAL(*QQ,std::string("56637059"));
AUTOCXX_ASSERT_EQUAL(*BLOG,std::string("http://blog.csdn.net/pandaxcl"));
AUTOCXX_ASSERT_EQUAL(*EMAIL,std::string("pandaxcl@163.com"));
AUTOCXX_ASSERT_EQUAL(*NETWORK,std::string("http://www.autodev.net"));
// 执行这个batch命令的撤销操作,具体意义视其记录的命令而定
bat.undo();// false表示撤销操作
// 检查状态
AUTOCXX_ASSERT_EQUAL(c.size(),5);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("熊春雷"));
AUTOCXX_ASSERT_EQUAL(*QQ,std::string("56637059"));
AUTOCXX_ASSERT_EQUAL(*BLOG,std::string("http://blog.csdn.net/pandaxcl"));
AUTOCXX_ASSERT_EQUAL(*EMAIL,std::string("pandaxcl@163.com"));
AUTOCXX_ASSERT_EQUAL(*NETWORK,std::string("http://www.autodev.net"));
// 执行这个batch命令的重做操作,具体意义视其记录的命令而定
bat.redo();// true表示重做操作
// 检查状态
AUTOCXX_ASSERT_EQUAL(c.size(),4);
AUTOCXX_ASSERT_EQUAL(*PANDAXCL,std::string("PANDAXCL"));
//AUTOCXX_ASSERT_EQUAL(*QQ,std::string("56637059"));
AUTOCXX_ASSERT_EQUAL(*BLOG,std::string("http://blog.csdn.net/pandaxcl"));
AUTOCXX_ASSERT_EQUAL(*EMAIL,std::string("pandaxcl@163.com"));
AUTOCXX_ASSERT_EQUAL(*NETWORK,std::string("http://www.autodev.net"));
可以添加额外信息的命令中心:
template<class Command>class basic_undo_bat:public basic_batch<Command>{};
template<class Command>class basic_redo_bat:public basic_batch<Command>{};
template<class Command>class basic_center
:public basic_undo_bat<Command>
,public basic_redo_bat<Command>// 命令中心,管理两种命令队列
{
private:
size_t _limit;
public:
typedef Command command_type;
typedef basic_batch <Command> batch_type;
typedef basic_redo_bat<Command> redo_type;
typedef basic_undo_bat<Command> undo_type;
typedef undo_type history_type;
basic_center():_limit(100)// 默认最大撤销操作步骤总数为100步,一般的应用已经足够
{
}
virtual~basic_center()
{
}
public:
size_t limit()// 获取撤销命令队列的最大命令数量
{
return _limit;
}
void limit(size_t Limit)// 设置撤销命令队列的最大命令数量
{
_limit=Limit;
relimit();
}
void relimit()// 检查撤销命令队列是否已满,剔除过期命令
{
typedef std::list<command_type*> QT;
if(_limit >= undo_type::size())return;
while(_limit < undo_type::size())
{
command_type *pCmd = undo_type::front();
undo_type::pop_front();
assert( NULL != pCmd );
delete pCmd;// 命令被释放了
}
}
public:
bool undoable()
{
return !undo_type::empty();
}
bool redoable()
{
return !redo_type::empty();
}
void undo()// 撤销
{
if (!undo_type::empty()) {
undo_type::back()->undo();
redo_type::push_back(undo_type::back());
undo_type::pop_back();
}
}
void redo()// 执行或者重做
{
if (!redo_type::empty()) {
redo_type::back()->redo();
undo_type::push_back(redo_type::back());
redo_type::pop_back();
}
}
void undo(int number)// 一次撤销很多步命令
{
for(int i=0;i<number;i++) {
if(undo_type::empty())break;
else undo();
}
}
void redo(int number)// 一次重做很多命令
{
for(int i=0;i<number;i++) {
if(redo_type::empty())break;
else redo();
}
}
// 下面的record、execute和stop三个函数实现复合命令的自动创建,只要保证如下
// 的固定格式即可:
// record();// 复合命令开始
// execute(命令1);// 子命令
// execute(命令2);
// record();// 子复合命令开始
// ... ...
// stop();// 子复合命令结束
// ... ...
// execute(命令n);
// stop();// 复合命令结束
// 这种组合方式可以任意的嵌套,从而实现无级撤销
void record(command_type*pCmd=NULL)
{
history_type::record(pCmd);
redo_type::free();
}
// 下面的execute也可以单独使用,不需要record和stop的配合
void execute(command_type*pCmd)
{
assert(pCmd!=NULL);
pCmd->redo();
record(pCmd);
relimit();
}
void stop()
{
history_type &H=static_cast<history_type&>(*this);
typename history_type::iterator result=H.begin();
typename history_type::reverse_iterator rit;
rit = std::find(H.rbegin(),H.rend(),static_cast<command_type*>(NULL));
std::advance(result,std::distance(rit,H.rend())-1);
if(result!=H.end())
{
typename history_type::difference_type diff;
diff = std::distance(result,H.end());
if(diff==1 || diff==2) {
H.erase(result);
}else{
batch_type*pBat=new batch_type();
pBat->splice(pBat->begin(),H,result,H.end());
pBat->remove(static_cast<command_type*>(NULL));//list
record(pBat);
}
}
}
};
下面是命令中心的扩展代码的基本状态的测试用例:
typedef undo::basic_command<MyExtraData> Command;
typedef undo::basic_container<std::string> CT;
typedef undo::basic_create<CT,Command> Create;
typedef undo::basic_modify<CT,Command> Modify;
typedef undo::basic_remove<CT,Command> Remove;
CT c;// 注意这里的容器类仍然是和撤销和重做概念是分离的,实际应用的过程可能是整合
// 在一起的,命令中心对象和容器对象是同一个对象,通过多重派生得到
// 在上面的代码中没有涉及到和一般的应用程序相关的类似概念,那么center模版类的出现这是
// 给出了这个非常近似的概念
undo::basic_center<Command> cc;// 和容器类的名称都及其类似,当然可以整合了,这里不这样做
CT::object_type* PANDAXCL = NULL;
CT::object_type* QQ = NULL;
CT::object_type* BLOG = NULL;
CT::object_type* EMAIL = NULL;
CT::object_type* NETWORK = NULL;
// 下面用center命令中心的观点来重新实现上面的功能
// 注意center对象的execute已经不可以直接使用了,为的就是下面的这个概念:
// 每一个操作都是立即执行的,这和一般的应用程序里面的操作概念非常类似
AUTOCXX_ASSERT_EQUAL(c.size(),0);
cc.execute(new Create(c,PANDAXCL));
AUTOCXX_ASSERT_EQUAL(c.size(),1);
cc.execute(new Create(c,QQ));
AUTOCXX_ASSERT_EQUAL(c.size(),2);
cc.execute(new Create(c,BLOG));
AUTOCXX_ASSERT_EQUAL(c.size(),3);
cc.execute(new Create(c,EMAIL));
AUTOCXX_ASSERT_EQUAL(c.size(),4);
// 撤销一步操作
cc.undo();
AUTOCXX_ASSERT_EQUAL(c.size(),3);
// 重做一步操作
cc.redo();
AUTOCXX_ASSERT_EQUAL(c.size(),4);
// 撤销多步操作
cc.undo(3);
AUTOCXX_ASSERT_EQUAL(c.size(),1);
// 重做多步操作
cc.redo(3);
AUTOCXX_ASSERT_EQUAL(c.size(),4);
//撤销多步操作(如果撤销的步骤数量比实际的可撤销数量多,多余的部分没有任何效果)
cc.undo(100);// 注意这里的实际可撤销步骤只有4步
AUTOCXX_ASSERT_EQUAL(c.size(),0);
// 重做多步操作
cc.redo(200);
AUTOCXX_ASSERT_EQUAL(c.size(),4);
// 撤销多步操作
cc.undo(3);
AUTOCXX_ASSERT_EQUAL(c.size(),1);
// 重做操作的步骤数量和先前撤销操作的数量不等
cc.redo(1);
AUTOCXX_ASSERT_EQUAL(c.size(),2);
// 再次全部撤销
cc.undo(100);
AUTOCXX_ASSERT_EQUAL(c.size(),0);
// 再次全部重做
cc.redo(100);
AUTOCXX_ASSERT_EQUAL(c.size(),4);
// 当出现连续进行撤销操作的时候
cc.undo(1);
AUTOCXX_ASSERT_EQUAL(c.size(),3);
cc.undo(1);
AUTOCXX_ASSERT_EQUAL(c.size(),2);
cc.undo(1);
AUTOCXX_ASSERT_EQUAL(c.size(),1);
cc.redo(3);
AUTOCXX_ASSERT_EQUAL(c.size(),4);
// 当出现进行撤销操作的时候又开始了新命令的时候,就会把先前已经存在的已
// 经撤销过的可重做操作清除也就是说这里存在一个不可撤销过程,实际上相当
// 于我们做事的时候,刚开始做错了,恢复原始状态之后马上进行重新选择的过
// 程,当然不需要记录之前我们做错过的事情:(
cc.undo(2);
AUTOCXX_ASSERT_EQUAL(c.size(),2);
// 这个时候又开始了一个新的命令
cc.execute(new Create(c,NETWORK));
AUTOCXX_ASSERT_EQUAL(c.size(),3);
// 在这种情况下考虑重做的过程
cc.redo(100);// 实际上什么也没有做,因为之前缓存的可重做队列被清空了:(
AUTOCXX_ASSERT_EQUAL(c.size(),3);
下面是命令中心的扩展代码的模拟复合命令的测试用例:
typedef undo::basic_command<MyExtraData> Command;
typedef undo::basic_container<std::string> CT;
typedef undo::basic_create<CT,Command> Create;
typedef undo::basic_modify<CT,Command> Modify;
typedef undo::basic_remove<CT,Command> Remove;
CT c;// 容器对象,每个对象必须放在一个同类型的容器中
undo::basic_center<Command> cc;// 命令中心
CT::object_type* PANDAXCL = NULL;
CT::object_type* QQ = NULL;
CT::object_type* BLOG = NULL;
CT::object_type* EMAIL = NULL;
CT::object_type* NETWORK = NULL;
// 现在来考虑一下之前考虑过的batch命令在center中是如何表示的
AUTOCXX_ASSERT_EQUAL(c.size(),0);
cc.record();
cc.execute(new Create(c,PANDAXCL));
cc.execute(new Create(c,EMAIL));
cc.execute(new Create(c,QQ));
cc.execute(new Create(c,BLOG));
cc.execute(new Create(c,NETWORK));
cc.stop();
AUTOCXX_ASSERT_EQUAL(c.size(),5);
// 撤销一步,实际上就是把上面的三个子命令组成的一个batch命令撤销了
cc.undo();
AUTOCXX_ASSERT_EQUAL(c.size(),0);
// 重做一步,实际上就是把上面的三个子命令组成的一个batch命令重做了
cc.redo();
AUTOCXX_ASSERT_EQUAL(c.size(),5);
// 上面的记录过程其实还可以嵌套子记录过程,相当于前面的例子中的子batch命令一样
cc.record();
// 没有任何命令也是可以接受的
cc.stop();
下面是命令中心的扩展代码的模拟子复合命令的测试用例:
typedef undo::basic_command<MyExtraData> Command;
typedef undo::basic_container<std::string> CT;
typedef undo::basic_create<CT,Command> Create;
typedef undo::basic_modify<CT,Command> Modify;
typedef undo::basic_remove<CT,Command> Remove;
CT c;// 容器对象,每个对象必须放在一个同类型的容器中
undo::basic_center<Command> cc;// 命令中心
CT::object_type* PANDAXCL = NULL;
CT::object_type* QQ = NULL;
CT::object_type* BLOG = NULL;
CT::object_type* EMAIL = NULL;
CT::object_type* NETWORK = NULL;
// 现在来考虑一下之前考虑过的batch命令在center中是如何表示的
AUTOCXX_ASSERT_EQUAL(c.size(),0);
cc.record();
{
cc.execute(new Create(c,PANDAXCL));
// 上面的记录过程其实还可以嵌套子记录过程,相当于前面的例子中的子batch命令一样
cc.record();
{
cc.execute(new Create(c,EMAIL));
cc.record();
{
cc.execute(new Create(c,QQ));
cc.execute(new Create(c,BLOG));
cc.execute(new Create(c,NETWORK));
}
cc.stop();
}
cc.stop();
}
cc.stop();
AUTOCXX_ASSERT_EQUAL(c.size(),5);
// 撤销一步,实际上就是把上面的三个子命令组成的一个batch命令撤销了
cc.undo();
AUTOCXX_ASSERT_EQUAL(c.size(),0);
// 重做一步,实际上就是把上面的三个子命令组成的一个batch命令重做了
cc.redo();
AUTOCXX_ASSERT_EQUAL(c.size(),5);
// 由上述测试可以看出,只要center的record函数和stop函数是配对的那么就可以无限制的
// 进行嵌套!当然,如果一个batch命令只有一个子命令的话就没有必要啦:)
// 注意加深嵌套层级就意味着性能损失,虽然它数量少的时候微不足道,但是一旦数量庞大
// 之后就很明显啦:)
好了,到此为止已经成功的完成了命令的扩展,使用方法并没有多大不同:)可以放心使 用了:)
发表于 @ 2007年10月11日 21:26:00 | 评论( loading... ) | 举报| 收藏