命令模式将“请求”封装成对象,这可以让你使用不同的请求、队列、或者日志请求来参数化其他对象。命令模式也支持撤销操作。
有时候在程序中需要在一个对象中处理很多个请求,这些请求对象没有实现统一的接口,需要写很多的判断语句加以区分不同的请求,然后执行相应的操作。命令模式提供了一种新的思路,通过创建一个新的对象命令对象,定义统一的方法名称,并且命令对象中有一个请求对象属性(组合)封装请求对象,在代码中调用命令对象统一的方法操作请求对象,不用写if语句区分不同的请求对象。
命令模式将请求封装在一个对象中,允许代码像操作对象一样去操作不同的方法,传递并且在合适的时机去调用请求对象的方法。有时候需要执行一个请求,但又不知道具体是什么请求,命令模式可以通过命令对象封装请求,把命令对象当作参数传递,执行命令对象中的方法。这样代码中操作的就是统一的命令对象,就可以把代码规范化。
在这我们以《headfirst设计模式》中的例子进行说明。有个遥控器有7个插槽,用来控制不同的电器,每个插槽对应两个按钮,开和关。遥控器有一个总的撤销按钮。如果遥控器的插槽有装置插入,按对应的开、关和撤销按钮就可以控制电器。下面是具体的代码实现。
package cn.lzz.hf.command;
/**
* 命令对象接口
* @author Administrator
*
*/
public interface Command {
/**
* 执行请求
*/
void execute();
/**
* 撤销请求
*/
void undo();
}
package cn.lzz.hf.command;
/**
* 电灯对象
* @author Administrator
*
*/
public class Light {
public void on(){
System.out.println("Light is on.");
}
public void off(){
System.out.println("Light is off.");
}
}
package cn.lzz.hf.command;
/**
* 关灯命令对象
* @author Administrator
*
*/
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light){
this.light=light;
}
@Override
public void execute() {
// TODO Auto-generated method stub
light.off();
}
@Override
public void undo() {
// TODO Auto-generated method stub
light.on();
}
}
package cn.lzz.hf.command;
/**
* 开灯命令对象
* @author Administrator
*
*/
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light){
this.light=light;
}
@Override
public void execute() {
// TODO Auto-generated method stub
light.on();
}
@Override
public void undo() {
// TODO Auto-generated method stub
light.off();
}
}
package cn.lzz.hf.command;
/**
* 收音机对象
* @author Administrator
*
*/
public class Stereo {
public void on(){
System.out.println("Stereo is on");
}
public void off(){
System.out.println("Stereo is off");
}
}
package cn.lzz.hf.command;
/**
* 关收音机命令对象
* @author Administrator
*
*/
public class StereoOffCommand implements Command {
private Stereo stereo;
public StereoOffCommand(Stereo stereo){
this.stereo=stereo;
}
@Override
public void execute() {
// TODO Auto-generated method stub
stereo.off();
}
@Override
public void undo() {
// TODO Auto-generated method stub
stereo.on();
}
}
package cn.lzz.hf.command;
/**
* 开收音机命令对象
* @author Administrator
*
*/
public class StereoOnCommand implements Command {
private Stereo stereo;
public StereoOnCommand(Stereo stereo){
this.stereo=stereo;
}
@Override
public void execute() {
// TODO Auto-generated method stub
stereo.on();
}
@Override
public void undo() {
// TODO Auto-generated method stub
stereo.off();
}
}
package cn.lzz.hf.command;
/**
* 空命令对象
* @author Administrator
*
*/
public class NoCommand implements Command {
@Override
public void execute() {
// TODO Auto-generated method stub
}
@Override
public void undo() {
// TODO Auto-generated method stub
}
}
package cn.lzz.hf.command;
import java.util.Stack;
/**
* 遥控器对象
* @author Administrator
*
*/
public class RemoteControl {
//打开按钮数组
private Command[] onCommands;
//关闭按钮数组
private Command[] offCommands;
//撤销命令栈
private Stack undoCommandStack;
/**
* 初始化
*/
public RemoteControl(){
onCommands=new Command[7];
offCommands=new Command[7];
Command tempCommand=new NoCommand();
for(int i=0;i<7;i++){
onCommands[i]=tempCommand;
offCommands[i]=tempCommand;
}
undoCommandStack=new Stack();
}
/**
* 设置命令对象
* @param soltIndex
* @param onCommand
* @param offCommand
*/
public void setCommand(int soltIndex,Command onCommand,Command offCommand){
onCommands[soltIndex]=onCommand;
offCommands[soltIndex]=offCommand;
}
/**
* on
* @param soltIndex
*/
public void onButtonWasPressed(int soltIndex){
onCommands[soltIndex].execute();
//记录命令对象
undoCommandStack.push(onCommands[soltIndex]);
}
/**
* off
* @param soltIndex
*/
public void offButtonWasPressed(int soltIndex){
offCommands[soltIndex].execute();
//记录命令对象
undoCommandStack.push(offCommands[soltIndex]);
}
/**
* 撤销方法
*/
public void undoButtonWasPressed(){
while(!undoCommandStack.isEmpty()){
undoCommandStack.pop().undo();
}
}
@Override
public String toString() {
StringBuilder stringBuilder=new StringBuilder();
stringBuilder.append("\n-------------- Remote Control ----------------\n");
for(int i=0;i
package cn.lzz.hf.command;
/**
* 测试代码
* @author Administrator
*
*/
public class RemoteControlTest {
public static void main(String[] args) {
//遥控器对象
RemoteControl control=new RemoteControl();
//电灯对象
Light light=new Light();
Command onLight=new LightOnCommand(light);
Command offLight=new LightOffCommand(light);
control.setCommand(0, onLight, offLight);
//收音机对象
Stereo stereo=new Stereo();
Command onStereo=new StereoOnCommand(stereo);
Command offStereo=new StereoOffCommand(stereo);
control.setCommand(1, onStereo, offStereo);
System.out.println("开灯-》关灯-》撤销");
control.onButtonWasPressed(0);
control.offButtonWasPressed(0);
control.undoButtonWasPressed();
System.out.println("关灯-》开灯-》撤销");
control.offButtonWasPressed(0);
control.onButtonWasPressed(0);
control.undoButtonWasPressed();
System.out.println("关闭收音机-》打开收音机-》撤销");
control.offButtonWasPressed(1);
control.onButtonWasPressed(1);
control.undoButtonWasPressed();
System.out.println("打开收音机-》关闭收音机-》撤销");
control.onButtonWasPressed(1);
control.offButtonWasPressed(1);
control.undoButtonWasPressed();
System.out.println("关灯-》打开收音机-》撤销");
control.offButtonWasPressed(0);
control.onButtonWasPressed(1);
control.undoButtonWasPressed();
}
}
执行结果
开灯-》关灯-》撤销
Light is on.
Light is off.
Light is on.
Light is off.
关灯-》开灯-》撤销
Light is off.
Light is on.
Light is off.
Light is on.
关闭收音机-》打开收音机-》撤销
Stereo is off
Stereo is on
Stereo is off
Stereo is on
打开收音机-》关闭收音机-》撤销
Stereo is on
Stereo is off
Stereo is on
Stereo is off
关灯-》打开收音机-》撤销
Light is off.
Stereo is on
Stereo is off
Light is on.
上面代码中,我们通过命令对象Command封装了Light和Stereo对象的开和关命令,在遥控器中可以调用参数对象Command,利用多态的特性模板化遥控器的代码,兼容不同的电器对象。如果需要增加新的电器,只需要创建相应的Command对象就可以实现,不用更改遥控器中的代码。
遥控器代码初始化时, 7个插槽设置了相同的空命令对象,这样在开、关按钮被按下的时候,不用判断插槽中是否有装置,也就是代码中不用判空。这是开发中常用的一中技巧。
命令模式有以下几个优点
1、命令模式通过命令对象把操作对象和请求对象解耦;
2、请求对象可以利用多态的特性复用具体的命令对象;
3、一个命令对象可以执行多个请求对象的具体操作;
4、增加一个具体的命令对象很容易。