常用设计模式系列(十七)—命令模式
常用设计模式系列(十七)—命令模式
第一节、前言
各位老铁好!
今天我来跟大家分享对象行为型模式第二章节——《命令模式》,“命令”一词,通俗易懂,我们在生活中经常会发出各种各样的命令,就像你告诉你手机上的“siri”、“小爱同学”打开音乐、切歌等等命令,而你的“语音小助理”根据不同的指令完成对不同软件的不同操作,这个过程使用的就是“命令模式”。
第二节、命令模式
命令模式概念:
命令模式(Command Pattern):
将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者或者记录请求日志以及支持可撤销的操作
命令模式的构成:
个人理解
将用户的请求即命令进行抽象,抽象为一个对象,每个客户端调用不同的对象即发起不同的命令来完成指令,命令模式需要支持当请求较多时候的请求队列排队,并且记录请求过程的日志,并且要支持可撤销的动作。
场景举例:
在日常生活中,我们使用开关来控制一些电器的打开或者关闭,例如常见的电灯、排风扇、门禁等等,这个开关可以安装到不同的电器上,那这样对应安装的开关就能控制对应的电器,所以开关与电器没有直接的关系,因为买来的开关可能控制灯泡,可能控制其它电器开关。但是开关与电器之间使用电线连接,开关打开时,电线通电,电器正常工作,相反则电器停止工作,相同的开关可以通过不同的线路来控制不同的电器。这个时候发送者(开关)换个接受者(电器)就可以解耦,发送的对象(开关)只需要知道如何发送请求,而不需要知道如何完成请求(完成请求是电线的职责),这个解耦后运转的模式,称为命令模式。
结构图:
第三节、场景分析及代码实现
场景分析
我们在使用电脑操控的时候,使用的是鼠标去点击系统的某个按钮,按钮上的字展示的不同的功能,然后点击按钮,按钮会根据不同的指令完成某个功能,例如某个系统有主页按钮,登录按钮、退出按钮;
可以分析有如下几个角色
1.功能按钮当做调用者。
2.Command类当做抽象命令。
3.可设计三个具体命令类:主页命令类、登录命令类、退出命令类。
4.可以设计出一个系统类用来当做接收者,包含访问主页命令、登录命令、退出命令
UML图
代码实现
1.调用者功能按钮类FunctionButton
package com.yang.command;/*** @ClassName FunctionButton* @Description 功能按钮类* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class FunctionButton {/*** 命令*/private Command command;public FunctionButton(Command command){this.command = command;}/*** 点击动作*/public void click(){//执行命令command.execute();}
}
2.抽象命令类Command
package com.yang.command;/*** @ClassName Command* @Description 抽象命令类* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public interface Command {/*** 执行命令*/public void execute();
}
3.访问主页命令IndexCommand
package com.yang.command;/*** @ClassName IndexCommand* @Description 访问主页命令类* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class IndexCommand implements Command{/*** 系统类*/private SystemMenu systemMenu;public IndexCommand(){systemMenu = new SystemMenu();}@Overridepublic void execute() {systemMenu.toIndex();}
}
4.登录命令类LoginCommand
package com.yang.command;/*** @ClassName LoginCommand* @Description 登录命令类* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class LoginCommand implements Command{/*** 系统类*/private SystemMenu systemMenu;public LoginCommand(){systemMenu = new SystemMenu();}@Overridepublic void execute() {systemMenu.toLogin();}
}
5.退出命令类LogoutCommand
package com.yang.command;/*** @ClassName LogOutCommand* @Description 退出命令类* @Author IT小白架构师之路* @Date 2021/1/25 14:51* @Version 1.0**/
public class LogOutCommand implements Command{/*** 系统类*/private SystemMenu systemMenu;public LogOutCommand(){systemMenu = new SystemMenu();}@Overridepublic void execute() {systemMenu.toLogOut();}
}
6.系统类作为调用者SystemMenu
package com.yang.command;/*** @ClassName SystemMenu* @Description 系统菜单类* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class SystemMenu {/*** 访问主页*/public void toIndex(){System.out.println("当前是主页");}/*** 登录动作*/public void toLogin(){System.out.println("登录成功");}/*** 退出动作*/public void toLogOut(){System.out.println("您已退出");}
}
7.创建客户端进行测试
package com.yang.command;/*** @ClassName Client* @Description 客户端* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class Client {public static void main(String[] args) {//要发起访问主页命令Command command = new IndexCommand();//功能按钮FunctionButton functionButton = new FunctionButton(command);//点击functionButton.click();System.out.println("-------------------我是分割线-----------------------");//要发起登录命令command = new LoginCommand();//功能按钮functionButton = new FunctionButton(command);//点击functionButton.click();System.out.println("-------------------我是分割线-----------------------");//要发起登出命令command = new LogOutCommand();//功能按钮functionButton = new FunctionButton(command);//点击functionButton.click();System.out.println("-------------------我是分割线-----------------------");}
}
8.测试结果如下
当前是主页
-------------------我是分割线-----------------------
登录成功
-------------------我是分割线-----------------------
您已退出
-------------------我是分割线-----------------------
使用代码实现队列
加队列的目的是,当业务发送方为多个、业务接收方为多个时,可以使用队列存储多个命令对象,不同的命令对象可以对应不同的请求者。增加队列后,可以将命令存放在队列中,一次执行,类似批量执行。
1.创建命令队列类
package com.yang.command;import java.util.ArrayList;/*** @ClassName CommandQueue* @Description 命令队列* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class CommandQueue {//队列private ArrayList<Command> commands = new ArrayList<Command>();//添加命令public void addCommand(Command command){commands.add(command);}//删除命令public void delCommand(Command command){commands.remove(command);}//执行public void execute(){for (Command command : commands){command.execute();}}
}
2.改造调用者为队列版FunctionButtonQueue
package com.yang.command;/*** @ClassName FunctionButtonQueue* @Description 调用者队列版* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class FunctionButtonQueue {//队列对象private CommandQueue commandQueue;public FunctionButtonQueue(CommandQueue commandQueue){this.commandQueue = commandQueue;}//处理public void click(){commandQueue.execute();}
}
3.客户端测试
package com.yang.command;/*** @ClassName ClientQueue* @Description 队列版测试* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class ClientQueue {public static void main(String[] args) {CommandQueue commandQueue = new CommandQueue();//要发起访问主页命令Command command = new IndexCommand();commandQueue.addCommand(command);//要发起登录命令command = new LoginCommand();commandQueue.addCommand(command);//要发起登出命令command = new LogOutCommand();commandQueue.addCommand(command);//执行FunctionButtonQueue functionButtonQueue = new FunctionButtonQueue(commandQueue);functionButtonQueue.click();}
}
4.执行结果
当前是主页
登录成功
您已退出
增加命令缓存来完成撤销
1.增加临时缓存类CommandCache
package com.yang.command;/*** @ClassName CommandCache* @Description 执行缓存* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class CommandCache {private static Command lastCommad;private static Command currentCommand;public static Command getLastCommad() {return lastCommad;}public static void setLastCommad(Command lastCommad) {CommandCache.lastCommad = lastCommad;}public static Command getCurrentCommand() {return currentCommand;}public static void setCurrentCommand(Command currentCommand) {CommandCache.currentCommand = currentCommand;}
}
2.优化功能按钮类
package com.yang.command;/*** @ClassName FunctionButton* @Description 功能按钮类* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class FunctionButton {/*** 命令*/private Command command;public FunctionButton(Command command){this.command = command;}/*** 点击动作*/public void click(){Command lastCommad = CommandCache.getLastCommad();Command currentCommand = CommandCache.getCurrentCommand();//都为空则为本次,第一次撤销无意义if(null == lastCommad && null == currentCommand){CommandCache.setCurrentCommand(command);CommandCache.setLastCommad(command);}else{//你不为空则把上次的最后一次进行执行CommandCache.setLastCommad(currentCommand);CommandCache.setCurrentCommand(command);}//执行命令command.execute();}public void revert(){System.out.println("--------撤销开始------");Command lastCommad = CommandCache.getLastCommad();lastCommad.execute();System.out.println("--------撤销结束------");}
}
3.测试撤销Client
package com.yang.command;/*** @ClassName RevertClient* @Description 注释* @Author IT小白架构师之路* @Date 2021/1/25* @Version 1.0**/
public class RevertClient {public static void main(String[] args) {//要发起访问主页命令Command command = new IndexCommand();//功能按钮FunctionButton functionButton = new FunctionButton(command);//点击functionButton.click();System.out.println("-------------------我是分割线-----------------------");functionButton.revert();//要发起登录命令command = new LoginCommand();//功能按钮functionButton = new FunctionButton(command);//点击functionButton.click();System.out.println("-------------------我是分割线-----------------------");//撤销functionButton.revert();}
}
4.测试结果,完成了撤销操作,回到了上一个界面
当前是主页
-------------------我是分割线-----------------------
--------撤销开始------
当前是主页
--------撤销结束------
登录成功
-------------------我是分割线-----------------------
--------撤销开始------
当前是主页
--------撤销结束------
第四节
优缺点及适用场景
优点
1.可以降低系统的耦合度,由于请求者和接收者之间不存在直接调用,则请求者与接收者之间做到了完全解耦,相同的请求者也可以做到调用不同的接受者,同样接收者可以给不同的请求者使用,互相独立,故降低了耦合度。
2.新的命令可以更快的增加到系统中,创建新的具体命令类不会影响其他类
3.可以简单的设计出一个队列命令进行批量执行
4.为撤销和恢复提供了一种设计和实现方案
缺点
1.当命令逐渐增加时,系统会增加很多的具体命令类。
2.当命令增多时,开发人员维护成本变高
适用场景
1.系统需要将调用者与接受者进行解耦,不需要两者进行交互的场景下。
2.系统要在不同的时间指定请求,将请求进行排队或者批量执行是。
3.系统需要增加通用的撤销、恢复功能时。
4.系统需要将一组操作指令进行抽象,完成通用设计时。
扫描二维码
关注我吧
IT小白架构师之路