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 总结
如果感觉文章难看,欢送点赞。
同时欢送关注微信公众号:氷泠之路。