常用设计模式系列(十七)---命令模式
第一节、前言
各位老铁好!
今天我来跟大家分享对象行为型模式第二章节------《命令模式》,"命令"一词,通俗易懂,我们在生活中经常会发出各种各样的命令,就像你告诉你手机上的"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();
}
@Override
public 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();
}
@Override
public 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();
}
@Override
public 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小白架构师之路
