1 概述
1.1 引言
日常生活中,能够通过开关管制一些电器的开启和敞开,比方电灯和排气扇。能够将开关了解成一个申请发送者,电灯是申请的最红接收者以及解决者,开关与电灯之间不存在间接的耦合关系,两者通过电线连贯在一起,使不同的电线能够连贯不同的申请接收者,只须要更换一根电线,雷同的发送者(开关)既可对应不同的接收者(电器)。
软件开发中常常须要向某些对象发送申请,然而并不知道具体的接收者是谁,也不晓得被申请的操作是哪个,此时心愿以一种松耦合的形式来设计软件,使得申请发送者与申请接收者之间可能打消彼此之间的耦合,让对象之间的调用关系更加灵便,能够灵便地指定申请接收者以及被申请的操作,此时能够应用命令模式进行设计。
命令模式能够将申请发送者和接收者齐全解耦,发送者与接收者之间没有间接援用关系,发送申请的对象只须要晓得如何发送申请,而不用晓得如何实现申请。
1.2 定义
命令模式:将一个申请封装成一个对象,从而可用不同的申请对客户进行参数化,对申请排队或者记录申请日志,以及反对可撤销的操作。
命令模式是一种对象行为型模式,别名为动作模式或者事务模式。
1.3 结构图
1.4 角色
Command
(形象命令类):形象命令类个别是一个抽象类或者接口,在其中申明了用于执行申请的execute()
办法,通过这些办法能够调用申请接收者的相干操作ConcreteCommand
(具体命令类):实现了形象命令类中申明的办法,对应具体的接收者对象,将接收者对象的动作绑定其中,在实现execute()
办法时,将调用接收者对象的相干操作Invoker
(调用者):调用者即申请发送者,通过命令对象来执行申请。一个调用者并不需要设计时确定接收者,因而它只与形象命令类之间存在关联关系。程序运行时将具体命令对象注入,并调用其中的execute()
办法,从而实现间接调用申请接收者的相干操作Receiver
(接收者):接收者执行与申请相干的操作,具体实现对申请的业务解决
2 典型实现
2.1 步骤
- 定义形象命令类:定义执行申请的办法
- 定义调用者:在调用办法外面蕴含对具体命令的调用,同时须要蕴含一个对形象命令的援用
- 定义接收者:定义接管申请的业务办法
- 定义具体命令类:继承 / 实现形象命令类,实现其中执行申请办法,转发到接收者的接管办法
2.2 形象命令类
这里实现为一个接口:
interface Command
{void execute();
}
2.3 调用者
class Invoker
{
private Command command;
public Invoker(Command command)
{this.command = command;}
public void call()
{System.out.println("调用者操作");
command.execute();}
}
调用者能够通过构造方法或者 setter 注入具体命令,对外提供一个调用办法call
,当调用此办法时调用具体命令的execute
。
2.4 接收者
class Receiver
{public void action()
{System.out.println("接收者操作");
}
}
这里的接收者只有一个action
,示意接管办法。
2.5 具体命令类
class ConcreteCommand implements Command
{private Receiver receiver = new Receiver();
@Override
public void execute()
{receiver.action();
}
}
具体命令类中须要蕴含一个对接收者的援用,以便在 execute
中调用接收者。
2.6 客户端
public static void main(String[] args)
{Invoker invoker = new Invoker(new ConcreteCommand());
invoker.call();}
通过构造方法注入具体命令到调用者中,接着间接调用即可。
输入如下:
3 实例
自定义功能键的设置,对于一个按钮,能够依据须要由用户设置为最小化 / 最大化 / 敞开性能,应用命令模式进行设计。
设计如下:
- 形象命令类:
Command
- 调用者:
Button
- 接收者:
MinimizeHandler
+MaximizeHandler
+CloseHandler
- 具体命令类:
MinimizeCommand
+MaximizeCommand
+CloseCommand
首先设计形象命令类,实现为一个接口,仅蕴含 execute
办法:
interface Command
{void execute();
}
接着是调用者类,蕴含一个形象命令的援用:
class Button
{
private Command command;
public Button(Command command)
{this.command = command;}
public void onClick()
{System.out.println("按钮被点击");
command.execute();}
}
而后是接收者类:
class MinimizeHandler
{public void handle()
{System.out.println("最小化");
}
}
class MaximizeHandler
{public void handle()
{System.out.println("最大化");
}
}
class CloseHandler
{public void handle()
{System.out.println("敞开");
}
}
最初是具体命令类,对应蕴含一个接收者成员即可,实现其中的 execute
并转发到接收者的办法:
class MinimizeCommand implements Command
{private MinimizeHandler handler = new MinimizeHandler();
@Override
public void execute()
{handler.handle();
}
}
class MaximizeCommand implements Command
{private MaximizeHandler handler = new MaximizeHandler();
@Override
public void execute()
{handler.handle();
}
}
class CloseCommand implements Command
{private CloseHandler handler = new CloseHandler();
@Override
public void execute()
{handler.handle();
}
}
测试类:
public static void main(String[] args)
{Button button = new Button(new MinimizeCommand());
button.onClick();
button = new Button(new MaximizeCommand());
button.onClick();
button = new Button(new CloseCommand());
button.onClick();}
输入:
如果须要新增一个命令,只须要命令接收者以及实现了 Command
的具体命令类,客户端再将具体命令注入申请发送者(Button
),毋庸间接操作申请接收者。
4 命令队列
有时候须要将多个申请排队,当一个申请发送者发送实现一个申请后,不止一个申请接收者产生响应,这些申请接收者将一一执行业务办法实现对申请的解决。这种模式能够通过命令队列实现,实现命令队列很简略,个别是减少一个叫 CommandQueue
的类,由该类负责存储多个命令对象,不同的命令对象能够对应不同的申请接收者,比方在下面的例子中减少 CommandQueue
命令队列类:
class CommandQueue
{private ArrayList<Command> commands = new ArrayList<>();
public void add(Command command)
{commands.add(command);
}
public void remove(Command command)
{commands.remove(command);
}
public void execute()
{System.out.println("批量执行命令");
commands.forEach(Command::execute);
}
}
接着批改调用者类 Button
(只需将原来的Command
改为CommandQueue
):
class Button
{
private CommandQueue queue;
public Button(CommandQueue queue)
{this.queue = queue;}
public void onClick()
{System.out.println("按钮被点击");
queue.execute();}
}
最初是客户端定义命令队列并作为参数传入调用者的构造方法或者 setter 中,最初由调用者执行办法:
public static void main(String[] args)
{CommandQueue queue = new CommandQueue();
queue.add(new MinimizeCommand());
queue.add(new MaximizeCommand());
queue.add(new CloseCommand());
Button button = new Button(queue);
button.onClick();}
输入如下:
5 撤销与重做
设计一个繁难计算器,实现加法性能,还可能实现撤销以及重做性能,应用命令模式实现。
设计如下:
- 形象命令类:
Command
- 调用者:
Calculator
- 接收者:
Adder
- 具体命令类:
AddCommand
首先先不实现撤销以及重做性能:
public class Test
{public static void main(String[] args)
{Calculator calculator = new Calculator(new AddCommand());
calculator.add(3);
calculator.add(9);
}
}
interface Command
{int execute(int value);
}
class Calculator
{
private Command command;
public Calculator(Command command)
{this.command = command;}
public void add(int value)
{System.out.println(command.execute(value));
}
}
class Adder
{
private int num = 0;
public int add(int value)
{return num += value;}
}
class AddCommand implements Command
{private Adder adder = new Adder();
@Override
public int execute(int value)
{return adder.add(value);
}
}
代码与下面的实例相似,就不解释了。
这里要害的问题是如何实现撤销以及重做性能,撤销可能复原到进行加法之前的状态,而重做能复原到进行了加法之后的状态,而且这是有固定程序的,因而能够联想到数组,应用下标示意以后状态,下标左移示意撤销,下标右移示意重做:
应用一个状态数组存储每次进行加法的状态,用下标示意以后状态,当撤销时,使下标左移,当重做时,使下标右移。
首先须要批改形象命令类,增加撤销以及重做办法:
interface Command
{int execute(int value);
int undo();
int redo();}
接着批改调用者类,增加撤销以及重做办法:
class Calculator
{
private Command command;
public Calculator(Command command)
{this.command = command;}
public void add(int value)
{System.out.println(command.execute(value));
}
public void undo()
{System.out.println(command.undo());
}
public void redo()
{System.out.println(command.redo());
}
}
外围的实现位于接收者类 Adder
,应用了List<Integer>
存储了状态,index
示意下标,在撤销或重做之前首先判断下标地位是否非法,非法则进行下一步操作:
class Adder
{private List<Integer> nums = new ArrayList<>();
private int index = 0;
public Adder()
{nums.add(0);
}
public int add(int value)
{int result = nums.get(index)+value;
nums.add(result);
++index;
return result;
}
public int redo()
{if(index + 1 < nums.size())
return nums.get(++index);
return nums.get(index);
}
public int undo()
{if(index - 1 >= 0)
return nums.get(--index);
return nums.get(index);
}
}
最初具体命令类简略增加撤销以及重做办法即可:
class AddCommand implements Command
{private Adder adder = new Adder();
@Override
public int execute(int value)
{return adder.add(value);
}
@Override
public int undo()
{return adder.undo();
}
@Override
public int redo()
{return adder.redo();
}
}
测试:
public static void main(String[] args)
{Calculator calculator = new Calculator(new AddCommand());
calculator.add(3);
calculator.add(9);
calculator.undo();
calculator.undo();
calculator.undo();
calculator.undo();
calculator.redo();
calculator.redo();
calculator.redo();
calculator.redo();}
6 次要长处
- 升高耦合度:因为请求者与接收者之间不存在间接援用,因而请求者与接收者之间实现齐全解耦,雷同的申请能够对应不同的接收者,同样雷同的接收者也能够供不同的请求者应用,两者之间具备良好的独立性
- 满足 OCP:新的命令能够很容易增加到零碎中,因为减少新的具体命令类不会影响到其余类,因而减少新的具体命令类很容易,满足 OCP 的要求
- 撤销 + 中作:为申请的撤销以及重做提供了一种设计和实现计划
7 次要毛病
- 过多具体命令类:应用命令模式可能会导致系统有过多的具体命令类,因为针对每一个申请接收者的调用操作都须要设计一个具体工具类,因而在某些零碎中可能须要提供大量的具体命令类
8 实用场景
- 零碎须要将申请调用者和申请接受者解耦,使得调用者和接收者不间接交互,申请调用者毋庸晓得接收者的存在,也无需晓得接收者是谁,接收者也毋庸关怀何时被调用
- 零碎须要在不同工夫指定申请,将申请排队和执行申请
- 零碎须要反对撤销以及复原操作
- 零碎须要将一组操作组合一起造成宏命令,应用命令队列实现
9 总结
如果感觉文章难看,欢送点赞。
同时欢送关注微信公众号:氷泠之路。