15.4 命令模式的扩展

15.4 命令模式的扩展

15.4.1 未讲完的故事

上面的例子我们还没有说完。想想看,客户要求增加一项需求,那是不是页面也增加, 同时功能也要增加呢?如果不使用命令模式,客户就需要先找需求组,然后找美工组,再找代码组……你想让客户跳楼啊!使用命令模式后,客户只管发命令模式,例如,需要增加一项需求,没问题,我内部调动三个组通力合作,然后把结果反馈给你,这也正是客户需要的。那这个要怎么修改呢?想想看,很简单的!在AddRequirementCommand类的execute方法中增加对PageGroup和CodePage的调用就可以了,修改后的代码如代码清单15-19所示。

代码清单15-19 修改后的增加需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AddRequirementCommand extends Command {
//执行增加一项需求的命令
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//页面也要增加
super.pg.add();
//功能也要增加
super.cg.add();
//给出计划
super.rg.plan();
}
}

看看,是不是解决问题了?客户Client只需要发布命令,至于如何执行这个命令,是协调一个对象,还是两个对象,都不需要关心,命令模式做了一层非常好的封装。

15.4.2 反悔问题

我们的例子说到这里是不是应该真的结束了?不,还有一个问题会经常发生的:客户发出命令,要撤回,怎么办?就类似你使用Ctrl+Z组合键(undo功能),发出一个命令,在没有执行(这时只要重新setCommand就可以了)或执行后撤回(执行后撤回是状态变更)该怎么实现呢?

有两种方法可以解决:一是结合备忘录模式还原最后状态,该方法适合接收者为状态的变更情况,而不适合事件处理;二是通过增加一个新的命令,实现事件的回滚。例子中的“删除一个页面”就需要一个反命令:撤销刚刚删除页面的命令,那客户发出这样一个命令,我们该怎么处理呢?

我们这样思考,反命令也是一个命令,那就是Command的一个子类,它实现的功能就是恢复刚刚删除的页面,然后我们再思考,谁能恢复删除的页面呢?当然是页面组了,于是作为接收者的页面组必须还有一个方法恢复最后删除的页面,也就是日志的回滚机制了,指定一个页面,回滚回去。分析完毕,我们来看实现,注意:以下为示意代码,请读者自行在应用中进行实现。修正后的Group如代码清单15-20所示。

代码清单15-20 修改后的Group类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class Group {
//甲乙双方分开办公,你要和那个组讨论,你首先要找到这个组
public abstract void find();
//被要求增加功能
public abstract void add();
//被要求删除功能
public abstract void delete();
//被要求修改功能
public abstract void change();
//被要求给出所有的变更计划
public abstract void plan();
//每个接收者都要对直接执行的任务可以回滚
public void rollBack(){
//根据日志进行回滚
}
}

仅仅增加了一个rollBack的方法,每个接收者都可以对自己实现的任务进行回滚。怎么回滚?根据事务日志进行回滚!新增加的一个命令CancelDeletePageCommand实现撤销刚刚发出的删除命令,如代码清单15-21所示。

代码清单15-21 撤销命令

1
2
3
4
5
6
public class CancelDeletePageCommand extends Command {
//撤销删除一个页面的命令
public void execute() {
super.pg.rollBack();
}
}

然后就是用Invoker进行调用了,客户选择了执行这个撤销动作,就可以进行撤销操作, 该示意代码确实比较简单,真正实现起来那是异常复杂的,为什么呢?事务日志处理是非常繁琐的处理机制,想想数据库的日志处理吧,你就能想象出这个日志有多复杂!