在阎宏博士的《JAVA与模式》一书中开头是这样描述命令(Command)模式的:
命令模式属于对象的行为模式。命令模式又称为行动(Action)模式或交易(Transaction)模式。
命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
1 命令模式的结构
命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。
每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
命令允许请求的一方和接收请求的一方能够独立演化,从而具有以下的优点:
-
命令模式使新的命令很容易地被加入到系统里。
-
允许接收请求的一方决定是否要否决请求。
-
能较容易地设计一个命令队列。
-
可以容易地实现对请求的撤销和恢复。
-
在需要的情况下,可以较容易地将命令记入日志。
下面以一个示意性的系统,说明命令模式的结构。
命令模式涉及到五个角色,它们分别是:
- **客户端(Client)角色:**创建一个具体命令(ConcreteCommand)对象并确定其接收者。
● **命令(Command)角色:**声明了一个给所有具体命令类的抽象接口。
● **具体命令(ConcreteCommand)角色:**定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
● **请求者(Invoker)角色:**负责调用命令对象执行请求,相关的方法叫做行动方法。
● **接收者(Receiver)角色:**负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
2 代码示例
以开发一个项目为例,这个项目的成员分工也是采用了常规的分工方式,分为需求组(Requirement Group,简称 RG)、美工组(Page Group,简称 PG)、代码组(我们内部还有一个比较优雅的名字:逻辑实现组,这里使用大家经常称呼的名称吧,英文缩写叫 Code Group,简称 CG)。
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 class PageGroup extends Group {
//首先这个美工组应该被找到吧,要不你跟谁谈?
public void find() {
System.out.println("找到美工组...");
}
//美工被要求增加一个页面
public void add() {
System.out.println("客户要求增加一个页面...");
}
//客户要求对现有界面做修改
public void change() {
System.out.println("客户要求修改一个页面...");
}
//甲方是老大,要求删除一些页面
public void delete() {
System.out.println("客户要求删除一个页面...");
}
//所有的增删改那要给出计划呀
public void plan() {
System.out.println("客户要求页面变更计划...");
}
}
public class CodeGroup extends Group {
//客户要求代码组过去和他们谈
public void find() {
System.out.println("找到代码组...");
}
//客户要求增加一项功能
public void add() {
System.out.println("客户要求增加一项功能...");
}
//客户要求修改一项功能
public void change() {
System.out.println("客户要求修改一项功能...");
}
//客户要求删除一项功能
public void delete() {
System.out.println("客户要求删除一项功能...");
}
//客户要求出变更计划
public void plan() {
System.out.println("客户要求代码变更计划...");
}
}
public class RequirementGroup extends Group {
//客户要求需求组过去和他们谈
public void find() {
System.out.println("找到需求组...");
}
//客户要求增加一项需求
public void add() {
System.out.println("客户要求增加一项需求...");
}
//客户要求修改一项需求
public void change() {
System.out.println("客户要求修改一项需求...");
}
//客户要求删除一项需求
public void delete() {
System.out.println("客户要求删除一项需求...");
}
//客户要求出变更计划
public void plan() {
System.out.println("客户要求需求变更计划...");
}
}
这时候客户需要修改需求了,定义出命令类:
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 {
//执行增加一项需求的命令
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//给出计划
super.rg.plan();
}
}
//删除一个页面的命令
public class DeletePageCommand extends Command {
//执行删除一个页面的命令
public void execute() {
//找到页面组
super.pg.find();
//删除一个页面
super.rg.delete();
//给出计划
super.rg.plan();
}
}
有了定义好得需求,我们需要一个跟客户对接得对接人:
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 xiaoSan = new Invoker(); //接头人就是我小三
//客户要求增加一项需求
System.out.println("-------------客户要求增加一项需求-----------------");
//客户给我们下命令来
Command command = new AddRequirementCommand();
//接头人接收到命令
xiaoSan.setCommand(command);
//接头人执行命令
xiaoSan.action();
}
}
3 总结
命令模式比较简单,但是在项目中使用是非常频繁的,封装性非常好,因为它把请求方(Invoker)和执 行方(Receiver)分开了,扩展性也有很好的保障。但是,命令模式也是有缺点的,你看 Command 的子类没 有,那个如果我要写下去的可不是几个,而是几十个,这个类膨胀的非常多,这个就需要大家在项目中自己考虑 使用了。