0x01前言
在阎宏博士(是谁?我也不知道,反正知道是大佬就行了)的《JAVA与模式》一书中开头是这样描述命令(Command)模式的:
命令模式属于对象的行为模式。命令模式又称为行动(Action)模式或交易(Transaction)模式。(这个..哲♂学模式,emmmm)
命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
0x02 命令模式的组成
Command
定义命令的接口,声明执行的方法。
ConcreteCommand
命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Receiver
接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker
要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client
创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
0x03 代码结构
// 抽象命令角色接口
public interface Command {
/**
* 执行方法
*/
void execute();
}
// 具体命令角色类
public class ConcreteCommand implements Command{
// 持有相应的接受对象
private Receiver receiver = null ;
// 构造方法
public ConcreteCommand(Receiver receiver) {
if (receiver != null ) {
this.receiver = receiver;
}
}
@Override
public void execute() {
// 转调接收者对象的相应方法,让接收者来执行真正的功能。
receiver.action();
}
}
// 接收者角色类
public class Receiver {
/**
* 真正执行命令相应的操作
*/
public void action(){
System.out.println("执行操作");
}
}
// 请求者
public class Invoke {
/**
* 持有命令对象
*/
private Command command = null ;
public Invoke(Command command) {
if (command != null) {
this.command = command;
}
}
/**
* 行动方法
*/
public void action(){
command.execute();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
// 创建接收者
Receiver receiver = new Receiver() ;
// 创建命令对象,设定它的接收者
Command concreteCommand = new ConcreteCommand(receiver);
// 创建请求者
Invoke invoke = new Invoke(concreteCommand);
// 执行操作
invoke.action();
}
}
0x04 举个实栗
上边是命令模式的一个简单的实现,现在我们需要举个简单的栗子看看命令模式的具体应用,电视机想必大家都见过,以电视机的开关命令为栗。
// 抽象命令类
public interface Command {
// 执行方法
void execute();
}
// 接收者角色类
public class TV {
// 打开方法
public void turnON(){
System.out.println("电视打开!");
}
// 关闭方法
public void turnOFF() {
System.out.println("电视关闭!");
}
}
// 具体命令角色类 --关闭命令
public class TvOFFCommand implements Command{
private TV tv ;
public TvOFFCommand(TV tv) {
this.tv = tv ;
}
@Override
public void execute() {
tv.turnOFF();
}
}
// 具体命令角色类 --打开命令
public class TvONCommand implements Command{
private TV tv ;
public TvONCommand(TV tv) {
this.tv = tv ;
}
@Override
public void execute() {
tv.turnON();
}
}
// 请求者 -- 遥控器
public class RemoteInvote {
private Command ON, OFF;
public RemoteInvote(Command ON,Command OFF) {
this.ON = ON ;
this.OFF = OFF ;
}
public void turnON() {
ON.execute();
}
public void turnOFF() {
OFF.execute();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
// 创建接收者
TV receiver = new TV();
// 创建命令对象 设定接收者
Command onCommand = new TvONCommand(receiver);
Command offCommand = new TvOFFCommand(receiver);
// 请求者 --创建遥控器
RemoteInvote invote = new RemoteInvote(onCommand, offCommand);
// 打开电视
invote.turnON();
// 关闭电视
invote.turnOFF();
}
}
结果
电视打开!
电视关闭!
0x05 关于例子的补充说明:
虽然代码看似挺多,但其实命令模式的结构还是比较清晰的,总的来说命令模式的使用流程就是首先创建一个抽象命令,然后创建多个具体命令实现抽象命令接口,然后创建一个命令接收者角色,它包含各种的行为的具体实现,然后再有一个命令调用者角色,提供给客户端,用于接收客户端的参数;
0x06 命令模式的扩展
由于命令模式篇(wo)幅(bu)太(xiang)长(xie),这里单独拆分出去:
- 命令模式扩展篇 - 宏命令:时间不够,栗子未定,代码暂无
- 命令模式扩展篇 - 撤销命令:时间不够,栗子未定,代码暂无
- 命令模式扩展篇 - 命令队列: 时间不够,栗子未定,代码暂无
- 命令模式扩展篇 - 请求日志:时间不够,栗子未定,代码暂无
敬请期待~
0x07 优缺点
- 优点
1.命令模式将行为调用者和各种行为分隔开,降低程序的耦合,便于程序扩展; 2.命令模式将行为的具体实现封装起来,客户端无需关心行为的具体实现; 3.命令模式可为多种行为提供统一的调用入口,便于程序对行为的管理和控制;
- 缺点
1.使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装,使用命令模式可能会导致系统有过多的具体命令类;(上面的例子就可以看出,每个命令都要有一个具体的命令类);
0x08 应用场景
1.希望将行为请求者和行为实现者解耦,不直接打交道; 2.希望分离掉行为请求者一部分的责任,行为请求者只需要将命令发给调用者,不再主动的去让行为实现者产生行为,符合单一职责原则; 3.希望可以控制执行的命令列表,方便记录,撤销/重做以及事务等功能; 4.期待可以将请求排队,有序执行; 5.希望可以将请求组合使用,即支持宏命令;
0x09 总结
命令模式最大的好处就是实现了行为请求者与行为实现者的解耦;
在实际场景中的使用:
Struts2中action中的调用过程中存在命令模式;
数据库中的事务机制的底层实现;
命令的撤销和恢复:增加相应的撤销和恢复命令的方法(比如数据库中的事务回滚);
例如:Java的Runnable就是命令模式的变形应用:
public class Test {
public static void main(String[] args) {
//范例
Runnable runnable = () -> System.out.println("具体命令"); // Command cmd = ConcreteCommand
Thread thread1 = new Thread(runnable); // 将 cmd 交给 Thread (Invoker)
thread1.start(); // Invoker 调用 cmd 的执行方法
}
}