在上一篇文章中,大概总结了单例模式,今天我要说的是另外一种模式即命令模式。废话不多说,直接进入主题。
命令模式的定义:将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销操作。
一看到这个定义是不是感觉有点懵圈,那么我们通过下面的例子来看看命令模式到底是怎么回事呢?
命令模式是对命令的封装,它把发出命令的责任(请求者)和执行命令的责任(接收者)分割开,委派给不同的对象。这就是把请求者和接收者解耦合。请求者只负责发起命令,它不需要知道接收者是如何去接收命令,也不需要知道接收者是如何具体的执行命令的。所以命令模式分为四个角色:
命令角色(Command):声明了一个给所有具体命令类的抽象接口。
具体命令角色(ConcreteCommand):定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
请求者角色(Invoker):负责调用命令对象执行请求,相关的方法叫做行动方法。
接收者角色(Receiver):负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
命令模式的结构可以用下面的图来示意:
这个图中说明了上面的四种角色是如何联系在一起的。下面我用代码来说明一下这几个角色是如何在命令模式中工作的。
接收者角色类:
public class Receiver {
/**
* 真正执行命令相应的操作
*/
public void action(){
System.out.println("执行操作");
}
}
抽象命令角色类:
public interface Command {
/**
* 执行方法
*/
void execute();
}
具体命令角色类:
public class ConcreteCommand implements Command {
//持有相应的接收者对象
private Receiver receiver = null;
/**
* 构造方法
*/
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
//通常会转调接收者对象的相应方法,让接收者来真正执行功能
receiver.action();
}
}
请求者角色类:
public class Invoker {
/**
* 持有命令对象
*/
private Command command = null;
/**
* 构造方法
* 这里是构造方法传入命令,也可以单独写个set方法来设置command。
*/
public Invoker(Command command){
this.command = command;
}
/**
* 行动方法
*/
public void action(){
command.execute();
}
}
下面我们有了上面的类,我们可以在来合理的使用命令模式了,如下:
public class Client {
public static void main(String[] args) {
//创建接收者
Receiver receiver = new Receiver();
//创建命令对象,设定它的接收者
Command command = new ConcreteCommand(receiver);
//创建请求者,把命令对象设置进去
Invoker invoker = new Invoker(command);
//执行方法
invoker.action();
}
}
针对上面的模式,我们举个具体的例子其中使用到命令模式。
遥控器(RemoteControl)
电视的遥控器本身有很多按钮,我们假设每个按钮就是一个命令,这样当我们按下一个按钮的时候其实就是再请求一个操作,然后由遥控器接收到命令并去对电视做相应的反应,我们不需要知道遥控器具体做了什么而让电视做相应的反应,我们只负责传达我们想要的命令给遥控器就可以了。
下面我们用代码先来实现遥控器上的两个功能:电源的开关,音量大小。
接收者角色,由遥控器扮演
public class RemoteControl {
public void turnOn(){
System.out.println("开机");
}
public void turnOff(){
System.out.println("关机");
}
public void soundOn(){
System.out.println("取消静音");
}
public void soundOff(){
System.out.println("静音");
}
}
抽象命令角色类
public interface Command {
/**
* 执行方法
*/
public void execute();
}
具体命令角色类
public class PowerOnCommand implements Command {
private RemoteControl mRemoteControl;
public PowerOnCommand(RemoteControl control){
mRemoteControl = control;
}
@Override
public void execute() {
mRemoteControl.turnOn();
}
}
public class PowerOffCommand implements Command {
private RemoteControl mRemoteControl;
public PowerOffCommand(RemoteControl control){
mRemoteControl = control;
}
@Override
public void execute() {
mRemoteControl.turnOff();
}
}
public class SoundOnCommand implements Command {
private RemoteControl mRemoteControl;
public SoundOnCommand(RemoteControl control){
mRemoteControl = control;
}
@Override
public void execute() {
mRemoteControl.soundOn();
}
}
public class SoundOffCommand implements Command {
private RemoteControl mRemoteControl;
public SoundOffCommand(RemoteControl control){
mRemoteControl = control;
}
@Override
public void execute() {
mRemoteControl.soundOff();
}
}
请求者角色,由键盘类扮演
public class Keyboard {
// 开机
private Command powerOn;
// 关机
private Command powerOff;
// 音量增
private Command soundOn;
// 音量减
private Command soundOff;
public void setPowerOn(Command powerOn) {
this.powerOn = powerOn;
}
public void setPowerOff(Command powerOff) {
this.powerOff= powerOff;
}
public void setSoundOn(Command soundOn) {
this.soundOn= soundOn;
}
public void setSoundOff(Command soundOff) {
this.soundOff = soundOff;
}
// 按下开机按钮
public void turnOn(){
powerOn.execute();
}
// 按下关机按钮
public void turnOff(){
powerOff.execute();
}
// 按下音量增按钮
public void soundOn(){
soundOn.execute();
}
// 按下音量减按钮
public void soundOff(){
soundOff.execute();
}
}
现在小红可以拿遥控器操作电视了。代码如下:
public class XiaoHong {
public static void main(String[]args){
// 创建接收者
RemoteControl rc = new RemoteControl();
// 创建命令对象
Command powerOnCommand = new PowerOnCommand(rc);
Command powerOffCommand = new PowerOffCommand(rc);
Command soundOnCommand = new SoundOnCommand(rc);
Command soundOffCommand = new SoundOffCommand(rc);
// 创建请求者
Keyboard keyboard = new Keyboard();
keyboard.setPowerOn(powerOnCommand);
keyboard.setPowerOff(powerOffCommand);
keyboard.setSoundOn(soundOnCommand);
keyboard.setSoundOff(soundOffCommand);
// 测试
keyboard.turnOn();
keyboard.turnOff();
keyboard.soundOn();
keyboard.soundOff();
}
}
这样我们就完成了对命令模式的使用。大家是不是对命令模式又有了更进一步的认识了呢?
大家想一想,我们上面都是按个按钮单个操作来执行的,那如果我们按一个按钮能不能执行一组操作呢?答案当然是可以的,而且这种实现方式就叫做宏命令。下面我们来看一下具体的实现。
宏命令
首先我们来定义一个宏命令的接口,该接口有两个方法:remove和add方法,并且这个接口实现了抽象命令接口(Command)
public interface MacroCommand extends Command {
/**
* 宏命令聚集的管理方法
* 可以添加一个成员命令
*/
public void add(Command cmd);
/**
* 宏命令聚集的管理方法
* 可以删除一个成员命令
*/
public void remove(Command cmd);
}
具体的宏命令MacroRemoteControlCommand类负责把个别的命令合成宏命令。
public class MacroRemoteControlCommand implements MacroCommand {
private List<Command> commandList = new ArrayList<Command>();
/**
* 宏命令聚集管理方法
*/
@Override
public void add(Command cmd) {
commandList.add(cmd);
}
/**
* 宏命令聚集管理方法
*/
@Override
public void remove(Command cmd) {
commandList.remove(cmd);
}
/**
* 执行方法
*/
@Override
public void execute() {
for(Command cmd : commandList){
cmd.execute();
}
}
}
下面我们假设有一个按钮,先是把音量调大,然后再把音量调小。当然这个按钮实际不存在(哈),我们只是为了测试做个假设。下面我们看怎么去实现一个按钮执行一组操作呢?
public class XiaoHong {
public static void main(String[]args){
// 创建接收者
RemoteControl rc = new RemoteControl();
// 创建命令对象
Command soundOnCommand = new SoundOnCommand(rc);
Command soundOffCommand = new SoundOffCommand(rc);
// 创建宏命令
MacroCommand mc = new MacroRemoteControlCommand();
mc.add(soundOnCommand);
mc.add(soundOffCommand);
mc.execute();
}
}
以上就是命令模式中宏命令的使用。那么到这里相信大家都对命令模式有了初步的认识,那么大家想一想在开发中有没有遇到过命令模式的例子呢?其实有一个就是线程池队列。每个新线程任务当做是一个命令对象放到队列中,然后线程池从队列中取出一个任务执行后,并销毁掉该命令对象。不知道大家还有没有想到别的可使用到命令模式的地方。
综合以上,总结一下命令模式的优点:
(1)命令模式使请求的发起者和接收者完全解耦
(2)命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
(3)命令模式中的命令对象能够很容易地组合成复合命令,也就是宏命令,从而使系统操作更简单,功能更强大。
(4)由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。
到这里就对命令模式做了个大概的分析讲解。