1. 命令模式:
把一个请求或者操作封装到一个对象中。从而允许系统使用不同的请求对客户进行参数化。对请求排队或者记录请求日志,还可以提供命令的撤销和恢复。是一种行为类模式。(说实话,这个定义基本无法让人理解!)。
命令模式是把每个命令进行封装,将命令请求发送者和命令接受者进行解耦。在这个模式中,请求者(持有具体的命令类)发送请求,具体的命令类(持有接收者)接受到后,执行接受者(具体操作的执行者)的具体操作方法,从而实现命令的执行。在这种模式下,命令请求者和命令命令接收者被完全分隔开来。请求者几乎不需要知道接收者的任何信息。
2. 命令模式包含的角色:
- 接收者角色: 负责执行或者实施具体的命令。所有类都可以成为接收者,去执行对应的行动方法。
- 抽象命令角色: 是一个给所有命令类继承的借口或者实现的抽象类,一般包含一个
execute()
方法。通过这个角色中的相关方法可以处理请求者的相关请求。 - 具体命令角色: 实现或继承类抽象命令接口或者类,它持有具体具体的接受者,并且将接收者和对应的命令或者动作进行绑定。并且将
execute()
方法绑定接收者的具体操作。 - 请求者角色: 持有对象命令,负责调用对象实现请求,一般情况下,该类通过与抽象命令角色之间进行关联。
3. 命令模式简单类图:
4. 命令模式示例代码:
场景:以常见的电视机遥控器为例。假设该遥控器有6个按钮,分别为:电视机开/关,音量加/减,频道加/减。
接收者角色:这里定义为电视机。
public class Television {
public void onTV(){
System.out.println("电视机正在启动中......");
}
public void offTV(){
System.out.println("电视机正在关闭中......");
}
public void volDown(){
System.out.println("电视机音量减小一度......");
}
public void volUp(){
System.out.println("电视机音量增加一度......");
}
public void channelUp(){
System.out.println("电视频道增+1......");
}
public void channelDown(){
System.out.println("电视机频道增加-1......");
}
}
抽象命令角色:
public interface Command {
void execute();
}
具体命令角色:
电视机开/关:
public class OffTvCommand implements Command {
//持有接收者对象
private Television television;
public OffTvCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.offTV();
}
}
public class OnTvCommand implements Command{
private Television television;
public OnTvCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.onTV();
}
}
音量加/减:
public class VolDownCommand implements Command {
private Television television;
public VolDownCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.volDown();
}
}
public class VolUpCommand implements Command {
private Television television;
public VolUpCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.volUp();
}
}
频道加/减:
public class ChannelDownCommand implements Command {
private Television television;
public ChannelDownCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.channelDown();
}
}
public class ChannelUpCommand implements Command {
private Television television;
public ChannelUpCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.channelUp();
}
}
请求者角色:这里定义为遥控器以及测试主函数:
遥控器:
public class ControlPanel {
private static final int CONTROL_SIZE = 7;
private Command[] commands;
/**
* 初始化所有按钮指向空的按钮,避免后面出现异常
*/
public ControlPanel() {
commands = new Command[CONTROL_SIZE];
for (int i = 0; i <commands.length ; i++) {
commands[i]=new NoCommand();
}
}
/**
* 把命令绑定到对应的键上
* @param index
* @param command
*/
public void addCommand(int index ,Command command){
commands[index] = command;
}
/**
* 模拟点击按钮
* @param index
*/
public void press(int index){
commands[index].execute();
}
}
测试主函数:
public class Main {
public static void main(String[] args) {
//初始化遥控器
ControlPanel controlPanel = new ControlPanel();
//初始化接收者
Television television = new Television();
//为遥控器上的键设置命令
controlPanel.addCommand(0,new ChannelDownCommand(television));
controlPanel.addCommand(1,new ChannelUpCommand(television));
controlPanel.addCommand(2,new OffTvCommand(television));
controlPanel.addCommand(3,new OnTvCommand(television));
controlPanel.addCommand(4,new ChannelDownCommand(television));
controlPanel.addCommand(5,new ChannelUpCommand(television));
//点击按钮
controlPanel.press(5);
controlPanel.press(4);
controlPanel.press(3);
controlPanel.press(2);
controlPanel.press(1);
controlPanel.press(0);
}
}
另外附上Nocommand源码:
public class NoCommand implements Command {
public NoCommand() {
}
@Override
public void execute() {
System.out.println("该按钮暂时还未绑定命令!");
}
}
进阶版: 但是用户又要求按下某个按钮后,既可以实现打开电视机,又能加大一度音量,频道在原来基础上减一。
这种情况下,我们就需要新增一个命令了。具体代码实现如下:
public class GroupCommand implements Command{
private List<Command> commandList = new ArrayList<Command>();
/**
* 添加系列命令
* @param command
*/
public void addCommand(Command command){
commandList.add(command);
}
/**
* 移除命令
* @param command
*/
public void removeCommand(Command command){
commandList.remove(command);
}
@Override
public void execute() {
for (Command command:commandList
) {
command.execute();
}
}
}
测试主函数;
public class Main {
public static void main(String[] args) {
//初始化遥控器
ControlPanel controlPanel = new ControlPanel();
//初始化接收者
Television television = new Television();
//初始化系类命令
GroupCommand groupCommand = new GroupCommand();
groupCommand.addCommand(new OnTvCommand(television));
groupCommand.addCommand(new ChannelUpCommand(television));
groupCommand.addCommand(new VolDownCommand(television));
//为遥控器上的键设置命令
controlPanel.addCommand(0,new ChannelDownCommand(television));
controlPanel.addCommand(1,new ChannelUpCommand(television));
controlPanel.addCommand(2,new OffTvCommand(television));
controlPanel.addCommand(3,new OnTvCommand(television));
controlPanel.addCommand(4,new VolDownCommand(television));
controlPanel.addCommand(5,new VolUpCommand(television));
controlPanel.addCommand(6,groupCommand);
//点击按钮
controlPanel.press(5);
controlPanel.press(4);
controlPanel.press(3);
controlPanel.press(2);
controlPanel.press(1);
controlPanel.press(0);
//执行系列命令
System.out.println("==================分割线==================");
controlPanel.press(6);
}
}
以上代码可以看出命令模式具有以下优点:
- 降低系统的耦合度:命令的发起者和接受者完全解耦合,请求者和接受者之间可以进行自由组合,不会互相影响,具有良好的独立性。
- 更加良好的扩展性:因为新增加的具体命令类不会影响到其他类。满足JAVA中“开闭原则”的要求。
- 更简便的组合命令:可以比较容易地设计一个命令队列或宏命令(组合命令)。
- 命令可以支持撤销,实现一个undo()方法来回到execute()被执行前的状态。
以上代码也可以看出命令模式存在一个很明显的缺点:
当命令很多的时候,会出现很多很多的具体命令类。
总结:
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的execute()方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。