请用软件模拟开机过程
按下启动按钮
然后电源供电
主板开始加电自检
BIOS依次寻找其他设备的BIOS并且让他们初始化自检
开始检测CPU,内存,光盘,硬盘,光驱,串口,并口,软驱即插即用设备
进入系统引导
上面的过程可以抽象为如下:
客户端只是想要发出命令或者请求,不关心请求的真正接受者是谁,也不关心具体如何实现,而且同一个请求的动作可以有不同的请求内容,当然具体的处理功能也不一样。请问如何实现呢?
将一个请求封装为一个对象,从而是你可以用不同的请求对客户进行参数化,队请求排队或者请求日志,以及支持可撤销的操作。
在命令模式中,会定义一个命令的接口,用来约束所有的命令对象,然后提供具体的命令实现,每个命令实现对象是对客户端某个请求的封装,对应于机箱上的按钮,一个机箱上可以有很多按钮,也就相当于会有多个具体的命令实现对象。在命令模式中,命令对象并不知道如何处理命令,会有相应的接受者对象来真正执行命令。
package demo12.command.example1; /** * 命令接口,声明执行的操作 */ public interface Command { /** * 执行命令对应的操作 */ public void execute(); }
package demo12.command.example1; /** * 具体的命令实现对象 */ public class ConcreteCommand implements Command { /** * 持有相应的接受者对象 */ private Receiver receiver = null; /** * 示意,命令对象可以有自己的状态 */ private String state; /** * 构造方法,传入相应的接受者对象 * @param receiver 相应的接受者对象 */ public ConcreteCommand(Receiver receiver){ this.receiver = receiver; } public void execute() { //通常会转调接受者对象的相应方法,让接受者来真正执行功能 receiver.action(); } }
package demo12.command.example1; /** * 接收者对象 */ public class Receiver { /** * 示意方法,真正执行命令相应的操作 */ public void action(){ //真正执行命令操作的功能代码 } }
package demo12.command.example1; /** * 调用者 */ public class Invoker { /** * 持有命令对象 */ private Command command = null; /** * 设置调用者持有的命令对象 * @param command 命令对象 */ public void setCommand(Command command) { this.command = command; } /** * 示意方法,要求命令执行请求 */ public void runCommand() { //调用命令对象的执行方法 command.execute(); } }
package demo12.command.example1; public class Client { /** * 示意,负责创建命令对象,并设定它的接受者 */ public void assemble() { // 创建接受者 Receiver receiver = new Receiver(); // 创建命令对象,设定它的接收者 Command command = new ConcreteCommand(receiver); // 创建Invoker,把命令对象设置进去 Invoker invoker = new Invoker(); invoker.setCommand(command); } }
机箱上的按钮就相当于命令对象
机箱相当于调用者
主板相当于接受者对象
命令对象持有一个接受者对象,相当于给机箱的按钮连了一根连接线
当机箱上的按钮被按下的时候,机箱就把这个命令通过连接线发送出去。
package demo12.command.example2; /** * 主板的接口 */ public interface MainBoardApi { /** * 主板具有能开机的功能 */ public void open(); }
package demo12.command.example2; /** * 技嘉主板类,开机命令的真正实现者,在Command模式中充当Receiver */ public class GigaMainBoard implements MainBoardApi{ /** * 真正的开机命令的实现 */ public void open(){ System.out.println("技嘉主板现在正在开机,请等候"); System.out.println("接通电源......"); System.out.println("设备检查......"); System.out.println("装载系统......"); System.out.println("机器正常运转起来......"); System.out.println("机器已经正常打开,请操作"); } }
package demo12.command.example2; /** * 命令接口,声明执行的操作 */ public interface Command { /** * 执行命令对应的操作 */ public void execute(); }
package demo12.command.example2; /** * 开机命令的实现,实现Command接口, * 持有开机命令的真正实现,通过调用接收者的方法来实现命令 */ public class OpenCommand implements Command{ /** * 持有真正实现命令的接收者——主板对象 */ private MainBoardApi mainBoard = null; /** * 构造方法,传入主板对象 * @param mainBoard 主板对象 */ public OpenCommand(MainBoardApi mainBoard) { this.mainBoard = mainBoard; } public void execute() { //对于命令对象,根本不知道如何开机,会转调主板对象 //让主板去完成开机的功能 this.mainBoard.open(); } }
package demo12.command.example2; /** * 机箱对象,本身有按钮,持有按钮对应的命令对象 */ public class Box { /** * 开机命令对象 */ private Command openCommand; /** * 设置开机命令对象 * @param command 开机命令对象 */ public void setOpenCommand(Command command){ this.openCommand = command; } /** * 提供给客户使用,接受并相应用户请求,相当于按钮被按下触发的方法 */ public void openButtonPressed(){ //按下按钮,执行命令 openCommand.execute(); } }
package demo12.command.example2; public class Client { public static void main(String[] args) { //1:把命令和真正的实现组合起来,相当于在组装机器, //把机箱上按钮的连接线插接到主板上。 MainBoardApi mainBoard = new GigaMainBoard(); OpenCommand openCommand = new OpenCommand(mainBoard); //2:为机箱上的按钮设置对应的命令,让按钮知道该干什么 Box box = new Box(); box.setOpenCommand(openCommand); //3:然后模拟按下机箱上的按钮 box.openButtonPressed(); } }
就是把请求也封装为对象
命令模式中通常会有一个命令的组装者,用它来维护虚实现和真实实现之间的关系。
并且发起请求的对象和真正实现的对象是解耦的。
可以用不同的命令对象,去参数化配置客户的请求。
实际上也很简单,举个例子:比如添加一个重启功能。
那么就在MainBoardApi上添加一个reset()函数即可,然后再重新写一个ResetCommand实现Command,当然box中也要添加一个重启的函数resetButtonPressed()就行啦。那么客户端调用就如下
package demo12.command.example3; public class Client { public static void main(String[] args) { //1:把命令和真正的实现组合起来,相当于在组装机器, //把机箱上按钮的连接线插接到主板上。 MainBoardApi mainBoard = new GigaMainBoard(); //创建开机命令 OpenCommand openCommand = new OpenCommand(mainBoard); //创建重启机器的命令 ResetCommand resetCommand = new ResetCommand(mainBoard); //2:为机箱上的按钮设置对应的命令,让按钮知道该干什么 Box box = new Box(); //先正确配置,就是开机按钮对开机命令,重启按钮对重启命令 box.setOpenCommand(openCommand); box.setResetCommand(resetCommand); //3:然后模拟按下机箱上的按钮 System.out.println("正确配置下------------------------->"); System.out.println(">>>按下开机按钮:>>>"); box.openButtonPressed(); System.out.println(">>>按下重启按钮:>>>"); box.resetButtonPressed(); //然后来错误配置一回,反正是进行参数化配置 //就是开机按钮对重启命令,重启按钮对开机命令 box.setOpenCommand(resetCommand); box.setResetCommand(openCommand); //4:然后还是来模拟按下机箱上的按钮 System.out.println("错误配置下------------------------->"); System.out.println(">>>按下开机按钮:>>>"); box.openButtonPressed(); System.out.println(">>>按下重启按钮:>>>"); box.resetButtonPressed(); } }
可撤销操作的意思就是:放弃该操作,回到未执行该操作的状态。
有两种基本思路:
1.补尝试,又称反操作式。
打开关闭 加减
2.存储恢复式
把操作前得状态记录下来。
package demo12.command.example4; /** * 操作运算的接口 */ public interface OperationApi { /** * 获取计算完成后的结果 * @return 计算完成后的结果 */ public int getResult(); /** * 设置计算开始的初始值 * @param result 计算开始的初始值 */ public void setResult(int result); /** * 执行加法 * @param num 需要加的数 */ public void add(int num); /** * 执行减法 * @param num 需要减的数 */ public void substract(int num); }
package demo12.command.example4; /** * 运算类,真正实现加减法运算 */ public class Operation implements OperationApi{ /** * 记录运算的结果 */ private int result; public int getResult() { return result; } public void setResult(int result) { this.result = result; } public void add(int num){ //实现加法功能 result += num; } public void substract(int num){ //实现减法功能 result -= num; } }
package demo12.command.example4; /** * 命令接口,声明执行的操作,支持可撤销操作 */ public interface Command { /** * 执行命令对应的操作 */ public void execute(); /** * 执行撤销命令对应的操作 */ public void undo(); }
package demo12.command.example4; /** * 具体的加法命令实现对象 */ public class AddCommand implements Command{ /** * 持有具体执行计算的对象 */ private OperationApi operation = null; /** * 操作的数据,也就是要加上的数据 */ private int opeNum; /** * 构造方法,传入具体执行计算的对象 * @param operation 具体执行计算的对象 * @param opeNum 要加上的数据 */ public AddCommand(OperationApi operation,int opeNum){ this.operation = operation; this.opeNum = opeNum; } public void execute() { //转调接收者去真正执行功能,这个命令是做加法 this.operation.add(opeNum); } public void undo() { //转调接收者去真正执行功能 //命令本身是做加法,那么撤销的时候就是做减法了 this.operation.substract(opeNum); } } package demo12.command.example4; /** * 具体的减法命令实现对象 */ public class SubstractCommand implements Command{ /** * 持有具体执行计算的对象 */ private OperationApi operation = null; /** * 操作的数据,也就是要减去的数据 */ private int opeNum; /** * 构造方法,传入具体执行计算的对象 * @param operation 具体执行计算的对象 * @param opeNum 要减去的数据 */ public SubstractCommand(OperationApi operation,int opeNum){ this.operation = operation; this.opeNum = opeNum; } public void execute() { //转调接收者去真正执行功能,这个命令是做减法 this.operation.substract(opeNum); } public void undo() { //转调接收者去真正执行功能 //命令本身是做减法,那么撤销的时候就是做加法了 this.operation.add(opeNum); } }
package demo12.command.example4; import java.util.*; /** * 计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮 */ public class Calculator { /** * 命令的操作的历史记录,在撤销时候用 */ private List<Command> undoCmds = new ArrayList<Command>(); /** * 命令被撤销的历史记录,在恢复时候用 */ private List<Command> redoCmds = new ArrayList<Command>(); private Command addCmd = null; private Command substractCmd = null; public void setAddCmd(Command addCmd) { this.addCmd = addCmd; } public void setSubstractCmd(Command substractCmd) { this.substractCmd = substractCmd; } public void addPressed(){ this.addCmd.execute(); //把操作记录到历史记录里面 undoCmds.add(this.addCmd); } public void substractPressed(){ this.substractCmd.execute(); //把操作记录到历史记录里面 undoCmds.add(this.substractCmd); } public void undoPressed(){ if(this.undoCmds.size()>0){ //取出最后一个命令来撤销 Command cmd = this.undoCmds.get(this.undoCmds.size()-1); cmd.undo(); //如果还有恢复的功能,那就把这个命令记录到恢复的历史记录里面 this.redoCmds.add(cmd ); //然后把最后一个命令删除掉, this.undoCmds.remove(cmd); }else{ System.out.println("很抱歉,没有可撤销的命令"); } } public void redoPressed(){ if(this.redoCmds.size()>0){ //取出最后一个命令来重做 Command cmd = this.redoCmds.get(this.redoCmds.size()-1); cmd.execute(); //把这个命令记录到可撤销的历史记录里面 this.undoCmds.add(cmd); //然后把最后一个命令删除掉 this.redoCmds.remove(cmd); }else{ System.out.println("很抱歉,没有可恢复的命令"); } } }
package demo12.command.example4; public class Client { public static void main(String[] args) { //1:组装命令和接收者 //创建接收者 OperationApi operation = new Operation(); //创建命令对象,并组装命令和接收者 AddCommand addCmd = new AddCommand(operation,5); SubstractCommand substractCmd = new SubstractCommand(operation,3); //2:把命令设置到持有者,就是计算器里面 Calculator calculator = new Calculator(); calculator.setAddCmd(addCmd); calculator.setSubstractCmd(substractCmd); //3:模拟按下按钮,测试一下 calculator.addPressed(); System.out.println("一次加法运算后的结果为:"+operation.getResult()); calculator.substractPressed(); System.out.println("一次减法运算后的结果为:"+operation.getResult()); //测试撤消 calculator.undoPressed(); System.out.println("撤销一次后的结果为:"+operation.getResult()); calculator.undoPressed(); System.out.println("再撤销一次后的结果为:"+operation.getResult()); //测试恢复 calculator.redoPressed(); System.out.println("恢复操作一次后的结果为:"+operation.getResult()); calculator.redoPressed(); System.out.println("再恢复操作一次后的结果为:"+operation.getResult()); } }
先说几个概念:
宏命令:多个命令的集合的命令
队列请求:就是对命令对象进行排队,组成工作队列,然后依次取出命令对象来执行。
存储恢复式的要点就在于将请求队列日志化,具体的案例我没有理解的很清楚, 以后遇到合适的例子再重新补充。
封装请求
需要抽象出需要执行的动作,并参数化这些对象
需要在不同时刻指定排列和执行请求
需要支持取消操作
当支持系统崩溃时,能将系统的操作功能重新执行一遍