COMMAND 模式
一.概述
命令模式,又称动作模式,目的是将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
二.类图
image三.结构
client:
装配者,用于创建命令对象以及设置命令对象的接收者
receiver:
接收者,封装了命令功能的具体实现,负责接收对象的调用请求并执行请求对应的方法
command
命令接口,规定了命令的执行方法
concretecommand
命令接口的具体实现,通过内部的接收者对象来完成命令要执行的功能
invoker
调用者,一般持有很多命令对象,负责组合,控制命令对象的调用,也是整个模式的核心。
四.实现
使用命令模式模拟文件的复制,粘贴,删除,移动操作
package cn.zzf.command.simple;
/**
* 接受者,也叫执行者
*
* @author GaoFeng2017
* @date 2018-06-02 09:00:40
**/
public class Receiver {
public void copy() {
System.out.println("复制文件");
}
public void paste() {
System.out.println("粘贴文件");
}
public void delete() {
System.out.println("删除文件");
}
public void move() {
System.out.println("移动文件");
}
}
package cn.zzf.command.simple;
/**
* 命令接口
*
* @author GaoFeng2017
* @date 2018-06-02 09:06:10
**/
interface Command {
/**
* 执行的方法
*/
void execute();
}
package cn.zzf.command.simple;
/**
* copy命令实现类
*
* @author GaoFeng2017
* @date 2018-06-02 09:11:52
**/
public class CopyCommand implements Command {
private Receiver receiver;
public CopyCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.copy();
}
}
package cn.zzf.command.simple;
/**
* delete命令实现类
*
* @author GaoFeng2017
* @date 2018-06-02 09:22:01
**/
public class DeleteCommand implements Command{
private Receiver receiver;
public DeleteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.delete();
}
}
package cn.zzf.command.simple;
/**
* @author GaoFeng2017
* @date 2018-06-02 09:31:29
**/
public class MoveCommand implements Command{
private Receiver receiver;
public MoveCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.move();
}
}
package cn.zzf.command.simple;
/**
* paste命令实现类
*
* @author GaoFeng2017
* @date 2018-06-02 09:16:10
**/
public class PasteCommand implements Command {
private Receiver receiver;
public PasteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.paste();
}
}
package cn.zzf.command.simple;
/**
* 调用者
* @author GaoFeng2017
* @date 2018-06-02 09:32:56
**/
public class Invoker {
private Command copyCommand;
private Command pasteCommand;
private Command deleteCommand;
public Invoker(Command copyCommand, Command pasteCommand, Command deleteCommand) {
this.copyCommand = copyCommand;
this.pasteCommand = pasteCommand;
this.deleteCommand = deleteCommand;
}
public void action() {
this.copyCommand.execute();
this.pasteCommand.execute();
this.deleteCommand.execute();
}
}
package cn.zzf.command.simple;
/**
* 装配者,用于创建命令对象以及设置命令对象的接收者
*
* @author GaoFeng2017
* @date 2018-06-02 09:40:50
**/
public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
CopyCommand copyCommand = new CopyCommand(receiver);
PasteCommand pasteCommand = new PasteCommand(receiver);
DeleteCommand deleteCommand = new DeleteCommand(receiver);
Invoker invoker = new Invoker(copyCommand,pasteCommand,deleteCommand);
invoker.action();
}
}
运行结果
复制文件
粘贴文件
删除文件
分析
- 在上面案例中,我们似乎没有感觉到这个模式的作用,甚者有点为了模式而模式的感觉,完成文件操作我们只需要在client中调用receiver对象中的方法即可,为何需要通过两个多余对象(invoker,command)来间接调用呢?
- 其实,每个模式都有自己的应用场景,上面的案例并不适合command模式,更多的只是为了展示模式的具体实现。command模式的核心在于将invoker和receiver进行了解耦,通过command的实现类把每个方法变成了对象(把行为抽象为对象),这样灵活了很多,既可以作为参数,又能做持久化。试想一下,如果我们的文件操作需要支持操作记录功能,那直接使用receiver对象的方法就要写很多重复的业务逻辑,但是我们可以在invoker中去编写这些逻辑,每当调用invoker时,便会执行记录逻辑,这些逻辑只存在一个方法中,得到了复用。
- 除了记录调用操作,我们还可以实现请求队列,撤销/恢复,甚至在不同的时间指定请求(两个请求的执行间隔时间不能小于1s)。我们发现,即使请求者不存在了,但是命令对象可能是活动的(持久化)。
五.案例
案例一: 请求队列,Command实现类和Receiver参考上面的实现
package cn.zzf.command.simple;
import java.util.LinkedList;
/**
* 请求队列
*
* @author GaoFeng2017
* @date 2018-06-02 13:14:52
**/
public class QueueInvoker {
private LinkedList<Command> commands = new LinkedList<>();
public void addCommand(Command command) {
commands.add(command);
}
public void removeCommand(Command command) {
commands.remove(command);
}
public void action() {
for (Command command : commands) {
command.execute();
System.out.println("命令" + command + "已被记录");
}
}
}
package cn.zzf.command.simple;
/**
* 装配者,用于创建命令对象以及设置命令对象的接收者
*
* @author GaoFeng2017
* @date 2018-06-02 09:40:50
**/
public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
CopyCommand copyCommand = new CopyCommand(receiver);
PasteCommand pasteCommand = new PasteCommand(receiver);
DeleteCommand deleteCommand = new DeleteCommand(receiver);
// Invoker invoker = new Invoker(copyCommand,pasteCommand,deleteCommand);
QueueInvoker queueInvoker = new QueueInvoker();
queueInvoker.addCommand(new CopyCommand(receiver));
queueInvoker.addCommand(new PasteCommand(receiver));
queueInvoker.addCommand(new DeleteCommand(receiver));
queueInvoker.action();
}
}
运行结果
复制文件
命令cn.zzf.command.simple.CopyCommand@4554617c已被记录
粘贴文件
命令cn.zzf.command.simple.PasteCommand@74a14482已被记录
删除文件
命令cn.zzf.command.simple.DeleteCommand@1540e19d已被记录
案例二:撤销/重做操作,假设有一个画板,点击某个按钮在画板上画圆,画圆完成后,点击撤销按钮,可以撤销刚才画的圆,点击重做,可以把刚才的撤销的圆重新显示出来
package cn.zzf.command.undo;
/**
* @author GaoFeng2017
* @date 2018-06-02 16:01:09
**/
public interface Command {
void execute();
void cancel();
}
package cn.zzf.command.undo;
import java.util.UUID;
/**
* @author GaoFeng2017
* @date 2018-06-02 16:54:43
**/
public class DrawCircleCommand implements Command {
private String id = UUID.randomUUID().toString();
@Override
public void execute() {
//其他逻辑
System.out.println("画圆中....");
}
@Override
public void cancel() {
//其他逻辑
System.out.println("撤销圆...." + id);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DrawCircleCommand that = (DrawCircleCommand) o;
return id != null ? id.equals(that.id) : that.id == null;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
package cn.zzf.command.undo;
import java.util.Stack;
/**
* @author GaoFeng2017
* @date 2018-06-02 17:06:55
**/
public class Invoker {
private Stack<Command> cancels = new Stack<>();
private Stack<Command> recovers = new Stack<>();
private int cancelCount = 0;
public Invoker() {
}
public void execute(Command command) {
//执行命令
command.execute();
//把执行完的命令放入可撤销栈中
cancels.push(command);
//如果撤销栈超出撤销次数的限制,则删除栈尾的撤销命令
if (cancelCount > 0 && cancels.size() > cancelCount) {
cancels.remove(cancels.size() - 1);
}
//执行新命令后,重做栈要被清空(重做栈是针对撤销栈的)
if (!recovers.isEmpty()) {
recovers.clear();
}
}
public void cancel() {
//撤销栈如果有撤销命令就执行栈顶的命令对象的撤销操作,并从撤销栈中删除,最后存入到重做栈中
if (!cancels.isEmpty()) {
Command command = cancels.pop();
command.cancel();
recovers.push(command);
}
}
public void redo() {
//重做栈如果有重做命令,则执行并删除栈顶的重做命令
if (!recovers.isEmpty()) {
recovers.pop().execute();
}
}
}
package cn.zzf.command.undo;
/**
* @author GaoFeng2017
* @date 2018-06-02 17:14:28
**/
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
invoker.execute(new DrawCircleCommand());
invoker.execute(new DrawCircleCommand());
invoker.cancel();
invoker.cancel();
invoker.redo();
invoker.redo();
}
}
运行结果
画圆中....7c982650-8eea-483a-9d58-5aecca6bb384
画圆中....3176e910-43d9-4e53-a76c-57f35a6e5cf8
撤销圆....3176e910-43d9-4e53-a76c-57f35a6e5cf8
撤销圆....7c982650-8eea-483a-9d58-5aecca6bb384
画圆中....7c982650-8eea-483a-9d58-5aecca6bb384
画圆中....3176e910-43d9-4e53-a76c-57f35a6e5cf8
六.总结
- 命令模式实现了命令请求和命令执行之间的解耦,使其拥有不同的生命周期。并且将方法抽象为对象,通过单独的执行类来控制方法的调用,可以轻松的进行逻辑复用。
网友评论