文章目录
一、命令模式简介
Command模式也叫命令模式 ,是行为设计模式的一种。Command模式通过被称为Command的类封装了对目标对象的调用行为以及调用参数。
二、命令模式结构
将请求、命令、动作等封装成对象,这样可以让项目使用这些对象来 参数化其他对象。使得命令的请求者和执行者解耦。
三、命令模式的角色与职责
Command : Command抽象类。
ConcreteCommand :Command的具体实现类。
Receiver :需要被调用的目标对象。
Invorker : 通过Invorker执行Command对象。
四、命令模式的具体实现
进入了智能家居时代,大米想做一个集合智能家居遥控的控制器。
1、不使用命令模式
设计方案
采用功能绑定的设计方案。
类设计
大米早期的设计还不成型,只能做了粗糙的设计。
首先看大米的智能家电。
大米电灯:
// An highlighted block
package design.command.gys.nocommand;
public class Light {
private String name;
public Light(String name) {
super();
this.name = name;
}
public void on() {
System.out.println(name+" Light on");
}
public void off() {
System.out.println(name+" Light off");
}
}
大米电视机:
// An highlighted block
package design.command.gys.nocommand;
public class SmartTV {
private String name;
public SmartTV(String name) {
super();
this.name = name;
}
public void on() {
System.out.println(name+" SmartTV on");
}
public void off() {
System.out.println(name+" SmartTV off");
}
}
大米CD机:
// An highlighted block
package design.command.gys.nocommand;
public class CDplayer {
private String name;
private int i= 0;
public CDplayer(String name) {
super();
this.name = name;
}
public void on() {
System.out.println(name+" CD on");
}
public void off() {
System.out.println(name+" CD off");
}
public void volumeadd() {
if(i<=20)
i++;
System.out.println(name+" CD volumeadd: "+i);
}
public void volumesub() {
if(i>0)
i--;
System.out.println(name+" CD volumesub "+i);
}
}
再来看看大米的集成遥控器,4*2一共8个按钮。
// An highlighted block
package design.command.gys.nocommand;
public class Control {
private Light light;
private SmartTV TV;
private CDplayer Cdplayer;
public Control(Light light, SmartTV tV, CDplayer cdplayer) {
super();
this.light = light;
this.TV = tV;
this.Cdplayer = cdplayer;
}
public void pressleftbutton(int button)
{
switch(button) {
case 0: light.on();break;
case 1: TV.on();break;
case 2: Cdplayer.on();break;
case 3: Cdplayer.volumeadd();break;
default : break;
}
}
public void pressrightbutton(int button)
{
switch(button) {
case 0: light.off();break;
case 1: TV.off();break;
case 2: Cdplayer.off();break;
case 3: Cdplayer.volumesub();break;
default : break;
}
}
}
看来大米的控制器采用了最简单的switch语句,判断输入再执行相应的程序。好了,看看我们测试程序。
// An highlighted block
package design.command.gys.nocommand;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Control ct=new Control(new Light("bedroom"),new SmartTV("living room"), new CDplayer("dining room"));
ct.pressleftbutton(0);
ct.pressleftbutton(1);
ct.pressleftbutton(2);
ct.pressleftbutton(3);
ct.pressleftbutton(3);
ct.pressleftbutton(3);
ct.pressrightbutton(3);
ct.pressrightbutton(2);
ct.pressrightbutton(1);
ct.pressrightbutton(0);
}
}
测试的结果:
// An highlighted block
bedroom Light on
living room SmartTV on
dining room CD on
dining room CD volumeadd: 1
dining room CD volumeadd: 2
dining room CD volumeadd: 3
dining room CD volumesub 2
dining room CD off
living room SmartTV off
bedroom Light off
效果不错,第一代大米继承遥控器就这样面市了。
2、使用命令模式
随着家里的智能家电越来越多,功能也越来越强大,大米CEO准备升级一代遥控器。
设计方案
每个按钮可以编程动态绑定一个命令对象,而每个命令对象可以使用同一类智能家电进行参数化,具有较强的扩展性。
类设计
大米公司升级了之前的智能CD播放器:
// An highlighted block
package design.command.gys.appliances;
public class CDplayer {
private String name;
private int volume= 0;
public CDplayer(String name) {
super();
this.name = name;
}
public void on() {
System.out.println(name+" CDplayer on");
}
public void off() {
System.out.println(name+" CDplayer off");
}
public void start() {
System.out.println(name+" CDplayer plays music");
}
public void stop() {
System.out.println(name+" CDplayer stops music");
}
public void volumeadd() {
if(volume<=20)
volume++;
System.out.println(name+" CD volumeadd: "+volume);
}
public void volumesub() {
if(volume>0)
volume--;
System.out.println(name+" CD volumesub "+volume);
}
}
并且又推出了智能冰箱:
// An highlighted block
package design.command.gys.appliances;
public class Bridge {
private String name;
public Bridge(String name) {
super();
this.name = name;
}
public void on() {
System.out.println(name+" Bridge on");
}
public void off() {
System.out.println(name+" Bridge off");
}
}
推出这么多智能家电,原来的集成遥控器就不能用了。于是,负责人决定做一款能够让使用者自己编程使用的遥控器。
首先定义了一个抽象的命令的类:
// An highlighted block
package design.command.gys.command;
public abstract class Command {
public abstract void execute() ;
public abstract void undo() ;
}
这个类里面定义了两个抽象的方法一个执行,一个撤销用来执行相反的动作,在每个继承类中都实现。
接下来我们定义控制类。
灯控制类:
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.Light;
public class LightOnCommand extends Command{
private Light light;
public LightOnCommand(Light light) {
super();
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();
}
}
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.Light;
public class LightOffCommand extends Command{
private Light light;
public LightOffCommand(Light light) {
super();
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();
}
}
智能电视控制类:
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.SmartTV;
public class SmartTVOnCommand extends Command{
private SmartTV TV;
public SmartTVOnCommand(SmartTV TV) {
super();
this.TV = TV;
}
@Override
public void execute() {
// TODO Auto-generated method stub
TV.on();
}
@Override
public void undo() {
// TODO Auto-generated method stub
TV.off();
}
}
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.SmartTV;
public class SmartTVOffCommand extends Command{
private SmartTV TV;
public SmartTVOffCommand(SmartTV TV) {
super();
this.TV = TV;
}
@Override
public void execute() {
// TODO Auto-generated method stub
TV.off();
}
@Override
public void undo() {
// TODO Auto-generated method stub
TV.off();
}
}
冰箱控制类:
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.Bridge;
public class BridgeOnCommand extends Command{
private Bridge bridge;
public BridgeOnCommand(Bridge bridge) {
super();
this.bridge = bridge;
}
@Override
public void execute() {
// TODO Auto-generated method stub
bridge.on();
}
@Override
public void undo() {
// TODO Auto-generated method stub
bridge.off();
}
}
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.Bridge;
public class BridgeOffCommand extends Command{
private Bridge bridge;
public BridgeOffCommand(Bridge bridge) {
super();
this.bridge = bridge;
}
@Override
public void execute() {
// TODO Auto-generated method stub
bridge.off();
}
@Override
public void undo() {
// TODO Auto-generated method stub
bridge.on();
}
}
新智能CD控制类:
在打开的同时播放音乐,在关闭之前停止播放音乐
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.CDplayer;
public class CDplayerOnCommand extends Command{
private CDplayer player;
public CDplayerOnCommand(CDplayer player) {
super();
this.player = player;
}
@Override
public void execute() {
// TODO Auto-generated method stub
player.on();
player.start();
}
@Override
public void undo() {
// TODO Auto-generated method stub
player.stop();
player.off();
}
}
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.CDplayer;
public class CDplayerOffCommand extends Command{
private CDplayer player;
public CDplayerOffCommand(CDplayer player) {
super();
this.player = player;
}
@Override
public void execute() {
// TODO Auto-generated method stub
player.stop();
player.off();
}
@Override
public void undo() {
// TODO Auto-generated method stub
player.on();
player.start();
}
}
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.CDplayer;
public class CDplayerVolumeAdd extends Command{
private CDplayer player;
public CDplayerVolumeAdd(CDplayer player) {
super();
this.player = player;
}
@Override
public void execute() {
// TODO Auto-generated method stub
player.volumeadd();
}
@Override
public void undo() {
// TODO Auto-generated method stub
player.volumesub();
}
}
// An highlighted block
package design.command.gys.command;
import design.command.gys.appliances.CDplayer;
public class CDplayerVolumeSub extends Command{
private CDplayer player;
public CDplayerVolumeSub(CDplayer player) {
super();
this.player = player;
}
@Override
public void execute() {
// TODO Auto-generated method stub
player.volumesub();
}
@Override
public void undo() {
// TODO Auto-generated method stub
player.volumeadd();
}
}
另外我们定义了无操作的命令类,用在没有实现命令编程的按钮上:
// An highlighted block
package design.command.gys.command;
public class NoCommand extends Command{
public void execute() {};
public void undo() {};
}
下面是这个集成遥控器的设计。定义两个数组,左边五个右边五个,但是可以自由组合,不必与固定功能绑定。
// An highlighted block
package design.command.gys.control;
import java.util.Stack;
import design.command.gys.command.Command;
import design.command.gys.command.NoCommand;
public class Controller {
private Command[] leftcommands =new Command[5];
private Command[] rightcommands=new Command[5];
public Controller() {
super();
}
public void setLeftCommand(Command[] command) {
for(int i=0;i<command.length;i++)
leftcommands[i]=command[i];
for(int i=command.length;i<5;i++)
leftcommands[i]=new NoCommand();
}
public void setRightCommand(Command[] command) {
for(int i=0;i<command.length;i++)
rightcommands[i]=command[i];
for(int i=command.length;i<5;i++)
rightcommands[i]=new NoCommand();
}
public void pressLeft(int botton)
{
leftcommands[botton].execute();
}
public void pressRight(int botton)
{
rightcommands[botton].execute();
}
}
接下来我们看看二代遥控器能否实现我们需要的功能。给每个按钮绑定上我们需要的智能家电的命令类,并参数化我们已有的相应的家电。下面是代码:
// An highlighted block
package design.command.gys.control;
import design.command.gys.appliances.*;
import design.command.gys.command.*;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Light L1=new Light("bedroom");
SmartTV TV=new SmartTV("living room");
CDplayer player=new CDplayer("dining room");
Bridge bridge =new Bridge("dining room");
Controller ct=new Controller();
Command[] leftcommands=new Command[]{new LightOnCommand(L1),
new BridgeOnCommand(bridge),new CDplayerOnCommand(player),
new CDplayerVolumeAdd(player),new SmartTVOnCommand(TV)};
Command[] rightcommands=new Command[]{new LightOffCommand(L1),
new BridgeOffCommand(bridge),new CDplayerOffCommand(player),
new CDplayerVolumeSub(player),new SmartTVOffCommand(TV)};
ct.setLeftCommand(leftcommands);
ct.setRightCommand(rightcommands);
ct.pressLeft(0);ct.pressLeft(1);ct.pressLeft(2);ct.pressLeft(3);ct.pressLeft(4);
ct.pressRight(0);ct.pressRight(1);ct.pressRight(2);ct.pressRight(3);ct.pressRight(4);
}
}
看看测试结果吧:
// An highlighted block
bedroom Light on
dining room Bridge on
dining room CDplayer on
dining room CDplayer plays music
dining room CD volumeadd: 1
living room SmartTV on
bedroom Light off
dining room Bridge off
dining room CDplayer stops music
dining room CDplayer off
dining room CD volumesub 0
living room SmartTV off
完美运行。
这时候,设计者又想一个按钮能够控制所有的家电。于是,有涉及了下面的AllCommand类:
// An highlighted block
package design.command.gys.command;
public class AllCommand extends Command{
private Command[] commands ;
public AllCommand(Command[] commands) {
super();
this.commands = commands;
}
@Override
public void execute() {
// TODO Auto-generated method stub
for(Command c:commands)
c.execute();
}
@Override
public void undo() {
// TODO Auto-generated method stub
for(Command c:commands)
c.undo();
}
}
但是这样也不需要重新设计遥控器,我们来看测试程序:
// An highlighted block
package design.command.gys.control;
import design.command.gys.appliances.*;
import design.command.gys.command.*;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Light L1=new Light("bedroom");
SmartTV TV=new SmartTV("living room");
CDplayer player=new CDplayer("dining room");
Bridge bridge =new Bridge("dining room");
Controller ct=new Controller();
Command[] leftcommands=new Command[]{new LightOnCommand(L1),
new BridgeOnCommand(bridge),new CDplayerOnCommand(player),
new CDplayerVolumeAdd(player),new SmartTVOnCommand(TV)};
Command[] rightcommands=new Command[]{new LightOffCommand(L1),
new BridgeOffCommand(bridge),new CDplayerOffCommand(player),
new CDplayerVolumeSub(player),new SmartTVOffCommand(TV)};
ct.setLeftCommand(leftcommands);
ct.setRightCommand(rightcommands);
ct.pressLeft(0);ct.pressLeft(1);ct.pressLeft(2);ct.pressLeft(3);ct.pressLeft(4);
ct.pressRight(0);ct.pressRight(1);ct.pressRight(2);ct.pressRight(3);ct.pressRight(4);
System.out.println("---------------");
Controller cta=new Controller();
AllCommand leftcommand=new AllCommand(leftcommands);
AllCommand rightcommand=new AllCommand(rightcommands);
cta.setLeftCommand(new Command[] {leftcommand});
cta.setRightCommand(new Command[] {rightcommand});
cta.pressLeft(0);
cta.pressRight(0);
}
}
看一下运行结果:
// An highlighted block
bedroom Light on
dining room Bridge on
dining room CDplayer on
dining room CDplayer plays music
dining room CD volumeadd: 1
living room SmartTV on
bedroom Light off
dining room Bridge off
dining room CDplayer stops music
dining room CDplayer off
dining room CD volumesub 0
living room SmartTV off
---------------
bedroom Light on
dining room Bridge on
dining room CDplayer on
dining room CDplayer plays music
dining room CD volumeadd: 1
living room SmartTV on
bedroom Light off
dining room Bridge off
dining room CDplayer stops music
dining room CDplayer off
dining room CD volumesub 0
living room SmartTV off
设计者脑袋一拍,又想出一个注意,想要来个撤销按钮。
没办法,程序猿又要加班加点了,不过也不是太难,在类中添加一个栈,用于撤回操作,每次执行一个操作时,都会将操作进行压栈,在需要撤回时进行弹栈操作,并执行undo()。当然,栈的大小我们就需要顶起清空,不能无限制的扩大,在这个程序里,暂时不实现这个功能:
// An highlighted block
package design.command.gys.control;
import java.util.Stack;
import design.command.gys.command.Command;
import design.command.gys.command.NoCommand;
public class Controller {
private Command[] leftcommands =new Command[5];
private Command[] rightcommands=new Command[5];
private Stack<Command> stack=new Stack<>();
public Controller() {
super();
}
public void setLeftCommand(Command[] command) {
for(int i=0;i<command.length;i++)
leftcommands[i]=command[i];
for(int i=command.length;i<5;i++)
leftcommands[i]=new NoCommand();
}
public void setRightCommand(Command[] command) {
for(int i=0;i<command.length;i++)
rightcommands[i]=command[i];
for(int i=command.length;i<5;i++)
rightcommands[i]=new NoCommand();
}
public void pressLeft(int botton)
{
leftcommands[botton].execute();
stack.push(leftcommands[botton]);
}
public void pressRight(int botton)
{
rightcommands[botton].execute();
stack.push(leftcommands[botton]);
}
public void returnPrevious() {
stack.pop().undo();
}
}
在每个遥控器中,我们只需要对每个按钮进行命令类的绑定,在命令类里参数化具体的智能家电就可以了。
下面接着来测试这个类:
// An highlighted block
package design.command.gys.control;
import design.command.gys.appliances.*;
import design.command.gys.command.*;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Light L1=new Light("bedroom");
SmartTV TV=new SmartTV("living room");
CDplayer player=new CDplayer("dining room");
Bridge bridge =new Bridge("dining room");
Controller ct=new Controller();
Command[] leftcommands=new Command[]{new LightOnCommand(L1),
new BridgeOnCommand(bridge),new CDplayerOnCommand(player),
new CDplayerVolumeAdd(player),new SmartTVOnCommand(TV)};
Command[] rightcommands=new Command[]{new LightOffCommand(L1),
new BridgeOffCommand(bridge),new CDplayerOffCommand(player),
new CDplayerVolumeSub(player),new SmartTVOffCommand(TV)};
ct.setLeftCommand(leftcommands);
ct.setRightCommand(rightcommands);
ct.pressLeft(0);ct.pressLeft(1);ct.pressLeft(2);ct.pressLeft(3);ct.pressLeft(4);
ct.pressRight(0);ct.pressRight(1);ct.pressRight(2);ct.pressRight(3);ct.pressRight(4);
System.out.println("---------------");
Controller cta=new Controller();
AllCommand leftcommand=new AllCommand(leftcommands);
AllCommand rightcommand=new AllCommand(rightcommands);
cta.setLeftCommand(new Command[] {leftcommand});
cta.setRightCommand(new Command[] {rightcommand});
cta.pressLeft(0);
cta.pressRight(0);
System.out.println("---------------");
ct.pressLeft(0);ct.returnPrevious();
ct.pressLeft(1);ct.returnPrevious();
}
}
看一下运行结果:
// An highlighted block
bedroom Light on
dining room Bridge on
dining room CDplayer on
dining room CDplayer plays music
dining room CD volumeadd: 1
living room SmartTV on
bedroom Light off
dining room Bridge off
dining room CDplayer stops music
dining room CDplayer off
dining room CD volumesub 0
living room SmartTV off
---------------
bedroom Light on
dining room Bridge on
dining room CDplayer on
dining room CDplayer plays music
dining room CD volumeadd: 1
living room SmartTV on
bedroom Light off
dining room Bridge off
dining room CDplayer stops music
dining room CDplayer off
dining room CD volumesub 0
living room SmartTV off
---------------
bedroom Light on
bedroom Light off
dining room Bridge on
dining room Bridge off
到此,二代集成遥控器也面市了。
五、命令模式的应用场景
在面向对象的程序设计中,一个对象调用另一个对象,一般情况下的调用过程是:创建目标对象实例;设置调用参数;调用目标对象的方法。
但在有些情况下有必要使用一个专门的类对这种调用过程加以封装,我们把这种专门的类称作command类。
- 整个调用过程比较繁杂,或者存在多处这种调用。这时,使用Command类对该调用加以封装,便于功能的再利用。
- 调用前后需要对调用参数进行某些处理。
- 调用前后需要进行某些额外处理,比如日志,缓存,记录历史操作等。