假设某个项目组分为需求组(Requirement Group,简称RG)、美工组(Page Group,简称PG)、代码组(Code Group,简称CG),还有一个项目经理,刚开始的时候客户(甲方)很乐意和每个组探讨,比如和需求组讨论需求,和美工讨论页面,和代码组讨论实现,告诉他们修改这里,删除这里,增加这些等等,这是一种比较常见的甲乙方合作模式,甲方深入到乙方的项目开发中,我们把这个模式用类图表示一下:
这个类图很简单,客户和三个组都有交流,我们看看代码:
Group是命令模式中的命令接收者角色(Receiver),客户命令它增加功能、减少功能、列出计划等
/**
* @description: 项目组分成了三个组,每个组要接受增删改的命令
*/
public abstract class Group {
public abstract void add();
public abstract void delete();
public abstract void change();
public abstract void find(); // 客户要和某个小组沟通,必须先找到对应的小组
public abstract void plan(); // 客户要求某小组列出执行计划
}
/**
* @description: 需求组
*/
public class RequirementGroup extends Group {
@Override
public void add() {
System.out.println("客户要求增加一项需求...");
}
@Override
public void delete() {
System.out.println("客户要求删除一项需求...");
}
@Override
public void change() {
System.out.println("客户要求修改一项需求...");
}
@Override
public void find() {
System.out.println("找到需求组...");
}
@Override
public void plan() {
System.out.println("客户要求列出需求变更计划...");
}
}
/**
* @description: 美工组
*/
public class PageGroup extends Group {
@Override
public void add() {
System.out.println("客户要求增加一个页面...");
}
@Override
public void delete() {
System.out.println("客户要求删除一个页面...");
}
@Override
public void change() {
System.out.println("客户要求修改一个页面...");
}
@Override
public void find() {
System.out.println("找到美工组...");
}
@Override
public void plan() {
System.out.println("客户要求列出页面变更计划...");
}
}
/**
* @description: 代码组
*/
public class CodeGroup extends Group {
@Override
public void add() {
System.out.println("客户要求增加一个功能...");
}
@Override
public void delete() {
System.out.println("客户要求删除一个功能...");
}
@Override
public void change() {
System.out.println("客户要求修改某个功能...");
}
@Override
public void find() {
System.out.println("找到代码组...");
}
@Override
public void plan() {
System.out.println("客户要求列出代码变更计划...");
}
}
假设客户要求增加一项需求,看代码怎么写:
public class Client {
public static void main(String[] args) {
System.out.println("----------------客户要求增加一项需求----------------");
Group rg = new RequirementGroup();
rg.find(); // 找到需求组
rg.add(); // 增加需求
rg.plan(); // 列出需求变更计划
}
}
# 运行结果:
----------------客户要求增加一项需求----------------
找到需求组...
客户要求增加一项需求...
客户要求列出需求变更计划...
过了一段时间,客户发现有个页面太多余,要求删除这个页面,代码如下:
public class Client {
public static void main(String[] args) {
System.out.println("----------------客户要求删除一个页面----------------");
Group pg = new PageGroup();
pg.find(); // 找到美工组
pg.delete(); // 删除某个页面
pg.plan(); // 列出页面变更计划
}
}
问题来了,修改是可以的,但是每次都是叫一个组去,布置个任务,然后出计划,次次都这样,如果让你当甲方也就是客户,你烦不烦?而且这种方式很容易出错误,而且还真发生过,客户把美工叫过去了,要删除,可美工说需求是这么写的,然后客户又命令需求组过去,一次次的折腾,客户也烦躁了,于是和项目经理说:“我不管你们内部怎么安排,你就给我找个接头人,我告诉他怎么做,删除页面了,增加功能了,你们内部怎么处理我不管,我就告诉他我要干什么就成了...”,项目经理也很乐意这么做,于是我们改变一下类图:
Command抽象类:客户发给我们的命令,定义三个工作组的成员变量,供子类使用;定义一个抽象方法execute(),由子类来实现;
Invoker实现类:项目接头人,setComand接受客户发给我们的命令,action()方法是执行客户的命令(方法名写成action()是与command的execute()区分开,避免混淆)
修改后的代码如下:
public abstract class Command {
protected RequirementGroup rg = new RequirementGroup();
protected PageGroup pg = new PageGroup();
protected CodeGroup cg = new CodeGroup();
// 只要一个方法,你要我做什么事情
public abstract void execute();
}
两个具体的实现类:
public class AddRequirementCommand extends Command {
@Override
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//给出计划
super.rg.plan();
}
}
public class DeletePageCommand extends Command {
@Override
public void execute() {
// 找到美工组
super.pg.find();
// 删除一个页面
super.pg.delete();
// 给出计划
super.pg.plan();
}
}
Command抽象类还可以有很多的子类,比如增加一个功能命令(AddCodeCommand),删除一份需求命令(DeleteRequirementCommand)等等,这里就不描述了,都很简单。
我们再看看接头人,就是Invoker类的实现:
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void action() {
this.command.execute();
}
}
现在客户提出各种需求就简单的多了:
public class Client {
public static void main(String[] args) {
// 定义张三为接头人
Invoker zhangsan = new Invoker();
System.out.println("----------------客户要求增加一项需求----------------");
// 客户下命令
Command command = new AddRequirementCommand();
// 接头人接受命令
zhangsan.setCommand(command);
// 接头人执行命令
zhangsan.action();
}
}
# 执行结果
----------------客户要求增加一项需求----------------
找到需求组...
客户要求增加一项需求...
客户要求列出需求变更计划...
假如客户又提出了新的需求:删除页面,那么只要在Client中把Command command = new AddRequirementCommand();
换为Command command = new DeletePageCommand();
即可,这就是命令模式,命令模式的通用类图如下:
在这个类图中,我们看到三个角色:
- Receiver角色:这个就是干活的角色,命令传递到这里是应该被执行的,具体到上面我们的例子中就是Group的三个实现类;
- Command角色:就是命令,需要执行的所有命令都这里声明;
- Invoker角色:调用者,接收到命令,并执行命令,例子中项目经理就是这个角色;
命令模式比较简单,但是在项目中使用是非常频繁的,封装性非常好,因为它把请求方(Invoker)和执行方(Receiver)分开了,扩展性也有很好的保障。但是,命令模式也是有缺点的,如果需要扩展命令,就需要新增加Command类的子类,如果要新增的子类数量很多,那么是否使用命令模式就需要考虑了。
我们的例子还没有结束,通常来说,一个新增需求的命令,并不是通知给需求组就可以的,而是需要三个组通力合作的,那么我们在AddRequirementCommand命令中怎么调动其他组呢?很简单的,在AddRequirementCommand类的execute()方法中增加对PageGroup和CodePage的调用就成了,修改后的代码如下:
public class AddRequirementCommand extends Command {
@Override
public void execute() {
// 找到需求组
super.rg.find();
// 增加一份需求
super.rg.add();
// 页面要增加
super.pg.add();
// 功能也要增加
super.cg.add();
//给出计划
super.rg.plan();
}
}
那么,客户提出需求后,又取消了,这个该怎么实现呢?其实取消也是一个命令,比如叫做undo(),那么在Group抽象类中增加undo()方法,3个Group继承类中重写undo()方法,然后在UndoRequirementCommand类中调用3个Group继承类中的undo()方法就可以加上这个撤销命令的功能,扩展起来还是很简单的。
本文原书:
《您的设计模式》 作者:CBF4LIFE