Command Pattern
1. 什么是命令模式
命令模式(Command Pattern):将一个请求封装为一个对象,从而使用不同的参数调用不同的请求
命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
在命令模式中,发送者与接收者之间引入了新的命令对象,将发送者的请求封装在命令对象中,再通过命令对象来调用接收者的方法。
命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法。
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。
2. 命令模式类角色解析
命令模式结构图:
- Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
- ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。
- Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。**一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。**在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
- Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
3. 扩展用法
3.1 多个抽象方法
抽象命令类Command中,可以添加多个抽象方法,进行不同的操作。
3.2 宏命令:
和组合模式连用,构成宏命令,又称为组合命令。
宏命令是一个具体命令类,它拥有一个集合属性,在该集合中包含了对其他命令对象的引用。
当调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员可以是简单命令,还可以继续是宏命令。
3.3 使用命令队列
一个请求发送者发送一个请求时,会有多个请求接收者(Command)响应,这些请求接收者将逐个执行业务方法,完成对请求的处理。
将多个请求接受者(Command)封装在CommandQueue中。
代码如下:
class CommandQueue {
//定义一个ArrayList来存储命令队列
private ArrayList<Command> commands = new ArrayList<Command>();
public void addCommand(Command command) {
commands.add(command);
}
public void removeCommand(Command command) {
commands.remove(command);
}
//遍历调用每一个命令对象的execute()方法
public void execute() {
for (Command command : commands) {
command.execute();
}
}
}
相应的Invoker中代码:
class Invoker {
private CommandQueue commandQueue; //维持一个CommandQueue对象的引用
//构造注入
public Invoker(CommandQueue commandQueue) {
this. commandQueue = commandQueue;
}
//设值注入
public void setCommandQueue(CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}
//调用CommandQueue类的execute()方法
public void call() {
commandQueue.execute();
}
}
4. 实操感想
在实践中,应该正确区分出谁是Invoker,谁是Receiver,再用Command将他们联系起来。
Receiver如果具有共同的方法,可以放在抽象类Command中,避免了在每个ConcreteCommand中创建接收者。
这种模式分离了发出命令者和响应命令者,而且面对抽象Command编程,将具体Command设置给发出命令者即可,可以很方便的替换响应对象,也可以很方便的添加一个响应对象。具体可以参考下面这个示例,很容易理解。
5. 代码示例
一个界面中,每个Button的功能不尽相同,有的打开帮助文档,有的使该界面最小化等等。
/*
抽象命令类,其子类持有请求接收者,在execute方法中调用该接受者来处理请求。
命令模式分离了请求的发送者和接受者,请求的发送者调用命令类execute()方法,命令类在此方法中转接到真正的接受者处理方法action中.这就是命令模式.
所有的请求者都封装在Invoker中,并且面对Command编程,使之解耦.需要使用不同的具体功能时,替换Command即可
*/
abstract class Command {
public abstract void execute();
}
//具体命令类,打开帮助文档。持有Receiver,真正的请求处理类。
public class CommandHelper extends Command {
private HandlerHelper handler;
public CommandHelper() {
handler = new HandlerHelper();
}
@Override public void execute() {
handler.action();
}
}
public class HandlerHelper {
public void action() {
System.out.println("打开帮助文档");
}
}
//具体命令类,最小化。
public class CommandMinimize extends Command {
private HandlerMinimize handler;
public CommandMinimize() {
handler = new HandlerMinimize();
}
@Override public void execute() {
handler.action();
}
}
public class HandlerMinimize {
public void action() {
System.out.println("最小化");
}
}
/*
Invoker类,封装真正的请求,持有Command类,调用Command类的execute()方法。
*/
class Button {
private String name;
private Command command;
public Button(String name) {
this.name = name;
}
public void setCommand(Command command) {
this.command = command;
}
public void onClick() {
System.out.println("点击按钮" + name);
command.execute();
}
}
//界面对象,持有很多Button
class SettingWindow {
private String title;
private List<Button> buttons = new ArrayList<>();
public SettingWindow(String title) {
this.title = title;
}
public void addButton(Button button) {
buttons.add(button);
}
public void removeButton(Button button) {
buttons.remove(button);
}
public void testDisplay() {
for (Button button : buttons) {
button.onClick();
}
}
}
//测试类
class ZZMainClass {
public static void main(String[] args) {
SettingWindow settingWindow = new SettingWindow("一个界面");
//创建按钮
Button buttonOne = new Button("Button One");
buttonOne.setCommand(new CommandHelper());//设置该Button响应命令的对象
Button buttonTwo = new Button("Button Two");
buttonTwo.setCommand(new CommandMinimize());//设置该Button响应命令的对象
buttonOne.onClick();//点击,触发命令
buttonTwo.onClick();//点击,触发命令
//此处如果需要额外的响应事件,比如最大化,则继承Command类实现一个最大化命令响应对象即可。很方便。更改响应命令对象更方便,直接调用button.setCommand(新的Command)即可。
//放入一个Window中
settingWindow.addButton(buttonOne);
settingWindow.addButton(buttonTwo);
//settingWindow.testDisplay();//测试一下
}
}