我曾经写过一个小软件,故事是这样的:我们公司有一些设备在外面运行,有一天我接到一个任务,要求我编写一个算法对系统进行控制,改善系统的性能。根据大家的讨论很快制订了算法,我也很快编码完毕。但问题来了,我怎样进行测试,怎样验证这个算法呢?我可以在家里进行仿真,模拟系统的真实运行情况,但仿真终归仿真,我对算法是否真正有效心里没底;我更加不可能拿到外面测试,那可是商用系统,万一出了纰漏我可担当不起。
经过冥思苦想后(有一点夸张,呵呵),终于被我想出了一个办法。系统具有一样功能,可以由人机命令设置来进行数据采集,可以在运行期间产生运行记录存放在一个日志文件中,每一条运行记录对应着系统的一次执行状况,也对应着日志文件中的一行,具有固定的格式。在一个小时内,大约可以产生几千条这样的记录。
我要求系统维护人员在若干个典型时段进行采集,最后交到我手里就是几个日志文件,分别代表不同的时段系统运行的状况。假如我能够从这些日志文件中”还原“出系统的真实运行轨迹的话,那么我就可以有效的验证我们的算法。
我题目已经说了,这是一个运用Command模式的例子,因此我首先简单介绍一下Command模式。按照GoF的定义,Command模式的意图就是将一个请求封装为一个对象,从而你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,支持可撤销的操作。
Command模式的好处是可以将调用者和命令执行者解耦,有时候命令调用者并不知道命令要如何执行,例如你点击一个菜单项,但真正的执行是由应用程序决定。因此通过事先注册好的命令,利用多态技术,命令调用者即使不知道具体命令的类型也可以执行它(所有命令都提供一个Execute()的虚拟接口)。具体的Command对象是一个智能的对象,它知道相关的Context,所以它知道如何去执行具体的操作。
对我来说,我在这个项目中运用Command模式是想利用Command模式的另外一个优点,就是万一系统崩溃了,也可以通过已经备份日志来重新执行这些命令,从而重新”还原“整个系统。
现在我就有这些日志,我的目标就是通过它们来重新模拟整个系统的运行轨迹。
我的做法大致是这样的:
首先定义一个Command的抽象基类
struct Command {
unsigned int timestamp;
//other property
virtual void execute () = 0;
virtual ~Command() {}
};
然后定义3种不同类型的具体Command类
struct SuccessCommand : public Command {
void execute ()
{
//do something...
}
};
struct FailureCommand : public Command {
void execute ()
{
//do something...
}
};
struct TimerCommand : public Command {
void execute ()
{
//do something...
}
};
最后我定义一个std::vector< Command * >来存放这些Command的指针。
具体做法是从日志文件中读取满足特定条件的记录,比如说,某个小时内的所有记录。然后判断它们的状态是成功还是失败,来生成不同的命令。
std::vector< Command * > comvec;
if (GetSysStatus(str) == SUCC)
{
comvec.push_back(new SuccessCommand);
}
else
{
comvec.push_back(new FailureCommand);
}
处理完毕后,因为我的算法是把1小时平均等分成若干个时间段,在时间段边界(例如每1分钟,可灵活设置)开始启动算法进行控制,所以我生成了若干个TimerCommand,添加到comvec中。
//byPeriod为算法启动间隔,byHour为指定小时
for (int i=byPeriod ; i<60; i+=byPeriod )
{
pCommand = new TimerCommand();
pCommand->timestamp = byHour * 3600 * 1000 + i * 60 * 1000;
//other processing
comvec.push_back (pCommand);
}
由于每个Command都有一个timestamp时间戳字段,因此我可以根据timestamp来进行排序,这个很简单:
sort (comvec.begin(), comvec.end(), LessTimeStamp);
LessTimeStamp是我自定义的一个比较函数,根据时间戳排序
bool LessTimeStamp (const Command *com1, const Command *com2)
{
return com1->timestamp < com2->timestamp;
}
排完序后,一个符合我要求的Command序列就诞生了!现在我既得到了系统的真实运行轨迹,同时又把我的算法控制点插入其中,这样得到的仿真结果就是真实可信的。
vector< Command * >::iterator end = comvec.end();
for (vector< Command * >::iterator it = comvec.begin(); it != end; ++it)
{
(*it)->execute();
}
Command模式是强大的,我的体会是要学习它的精髓,但不必太拘泥于它的实现形式,对其他模式也一样。