Java设计模式及应用场景之《命令模式》

article/2025/10/20 14:06:26

文章目录

        • 一、命令模式定义
        • 二、命令模式的结构和说明
        • 三、命令模式示例
        • 四、命令模式扩展 -- 宏命令示例
        • 五、命令模式扩展 -- 可撤销和恢复操作示例
          • 1、反操作式(补偿式)
          • 2、存储恢复式
        • 六、命令模式扩展 -- 队列请求
        • 七、命令模式扩展 -- 日志请求
        • 八、命令模式的优点
        • 九、命令模式的应用场景

一、命令模式定义

将一个请求封装成一个对象,从而使你可用不同的请求把客户端参数化,对请求排队或者记录请求日志,以及支持可撤销和恢复操作。

二、命令模式的结构和说明

在这里插入图片描述

  • Command 命令接口,声明执行方法。
  • ConcreteCommand 命令接口的实现对象,是“虚”的实现,通常会持有接收者,通过调用接收者的方法,来完成命令要执行的操作。
  • Receiver 接收者,真正执行命令的对象。
  • Invoker 调用者,通常会持有命令,相当于使用命令模式的入口。

三、命令模式示例

我们用命令模式来模拟一个餐馆点餐的场景。从顾客点完餐,到餐被做出来,后厨是命令的接收者,服务员手拿无线点餐器是命令的发起者,无线点餐器上有每个餐品的命令按钮。

Command 命令接口,声明执行命令的方法

/*** 命令接口,声明执行的操作*/
public interface Command {/*** 执行命令对应的操作*/void execute();
}

NoodleCommand(做面)和 PieCommand(做馅饼)(命令接口的实现对象)

/*** 做面的命令*/
public class NoodleCommand implements Command{/*** 持有真正实现命令的接收者--后厨对象*/private Kitchen kitchen;public NoodleCommand(Kitchen kitchen) {this.kitchen = kitchen;}@Overridepublic void execute() {kitchen.noodle();}
}
/*** 做馅饼的命令*/
public class PieCommand implements Command{/*** 持有真正实现命令的接收者--后厨对象*/private Kitchen kitchen;public PieCommand(Kitchen kitchen) {this.kitchen = kitchen;}@Overridepublic void execute() {kitchen.pie();}
}

Waiter 调用者

*** 餐厅服务人员对象,持有下单操作命令*/
@Data
public class Waiter {/*** 做面条命令对象*/private Command noodleCommand;/*** 做馅饼命令对象*/private Command pieCommand;/*** 下达做面条的命令*/public void noodleCommandExecute(){noodleCommand.execute();}/*** 下达做馅饼的命令*/public void pieCommandExecute(){pieCommand.execute();}}

Kitchen 接收者

/*** 后厨类,接收做菜命令,真正的实现做菜功能,在Command模式中充当Receiver*/
public class Kitchen {/*** 做面条*/public void noodle(){System.out.println("正在做一碗美味的拉面。");}/*** 做馅饼*/public void pie(){System.out.println("正在做一个香喷喷的馅饼。");}}

以上,命令模式模拟的餐馆点餐已经设计好了,下边我们来模拟一下使用。

public static void main(String[] args) {// 创建后厨对象Kitchen kitchen = new Kitchen();// 创建做面和做馅饼的命令对象NoodleCommand noodleCommand = new NoodleCommand(kitchen);PieCommand pieCommand = new PieCommand(kitchen);// 创建一个服务员对象Waiter waiter = new Waiter();// 服务员手拿无线点菜机,设置有做面条和做馅饼命令触发按钮waiter.setNoodleCommand(noodleCommand);waiter.setPieCommand(pieCommand);// 客人A:服务员您好,我想来碗面waiter.noodleCommandExecute();// 客人B:服务员您好,我要来个馅饼waiter.pieCommandExecute();// 客人C:服务员您好,给我来碗面waiter.noodleCommandExecute();}

执行main方法后,得到下边的输出:

正在做一碗美味的拉面。
正在做一个香喷喷的馅饼。
正在做一碗美味的拉面。

四、命令模式扩展 – 宏命令示例

宏命令指的是:包含多个命令的命令,是一个命令的组合。

假设餐馆里来了一伙4个人,坐在了1号桌上,服务员过来招待,4个人都点完单后,服务员才将1号桌的菜单发送给后厨。

MenuCommand 宏命令对象

/*** 菜单对象,是个宏命令对象*/
public class MenuCommand implements Command{/*** 记录多个命令对象*/private List<Command> list = new ArrayList();/*** 点餐,将下单餐品加入到菜单中* @param command*/public void addCommand(Command command){list.add(command);}@Overridepublic void execute() {for(Command command : list){command.execute();}}
}

Waiter 调用者

/*** 餐厅服务人员对象,持有宏命令命令--菜单*/
@Data
public class Waiter {/*** 持有宏命令对象--菜单*/private MenuCommand menuCommand = new MenuCommand();/*** 顾客点餐* @param command*/public void orderDish(Command command){menuCommand.addCommand(command);}/*** 顾客点餐完毕,这里就是执行菜单这个组合命令*/public void orderOver(){menuCommand.execute();}}

以上,服务员就可以等同一桌所有顾客点完餐后,再将菜单记录的多个餐品一起发给后厨,下边我们来模拟一下使用。

public static void main(String[] args) {// 创建后厨对象Kitchen kitchen = new Kitchen();// 创建一个服务员对象Waiter waiter = new Waiter();// 创建做面和做馅饼的命令对象NoodleCommand noodleCommand = new NoodleCommand(kitchen);PieCommand pieCommand = new PieCommand(kitchen);// 服务员来到1号餐桌:我们有面条和馅饼,请问4位顾客想吃什么// 客人A:我要一碗面条waiter.orderDish(noodleCommand);// 客人B:给我也来一碗面条waiter.orderDish(noodleCommand);// 客人C:我要一个馅饼加一碗面条waiter.orderDish(pieCommand);waiter.orderDish(noodleCommand);// 客人D:我要一碗面条waiter.orderDish(noodleCommand);// 服务员:总共4碗面条,一个馅饼,4位请稍等。waiter.orderOver();}

执行main方法后,得到下边的输出:

正在做一碗美味的拉面。
正在做一碗美味的拉面。
正在做一个香喷喷的馅饼。
正在做一碗美味的拉面。
正在做一碗美味的拉面。

宏命令从本质上讲类似于普通命令,但又跟普通命令有些不同,宏命令包含有多个普通的命令对象。简单来说,执行一个宏命令,就是打包执行宏命令里所包含的所有命令对象。

五、命令模式扩展 – 可撤销和恢复操作示例

  可撤销指的是回到未执行该命令之前的状态(类似于 Ctrl+z),可恢复指的是取消上次的撤销动作(类似于 Ctrl+Shift+z)。
  有两种方式来实现可撤销的操作:1、反操作式,也叫补偿式,就是撤销时,执行相反的动作;2、存储恢复式,就是把操作前的状态记录下来,撤销时,直接恢复到上一状态。

1、反操作式(补偿式)

考虑一个计算器的例子,计算器有加、减、撤销和恢复功能,我们先用反操作式来示例一下。

Command 命令接口,声明执行、撤销操作

/*** 命令接口,声明执行、撤销操作*/
public interface Command {/*** 执行命令对应的操作*/void execute();/*** 执行撤销命令对应的操作*/void undo();}

AddCommand(加法命令)和 SubtractCommand(减法命令)(命令接口的实现对象)

/*** 加法命令*/
public class AddCommand implements Command{/*** 持有真正进行运算操作的对象*/private Processor processor;/*** 要加上的数值*/private int number;public AddCommand(Processor processor, int number) {this.processor = processor;this.number = number;}@Overridepublic void execute() {this.processor.add(number);}@Overridepublic void undo() {this.processor.substract(number);}}
/*** 减法命令*/
public class SubtractCommand implements Command{/*** 持有真正进行运算操作的对象*/private Processor processor;/*** 要减去的数值*/private int number;public SubtractCommand(Processor processor, int number) {this.processor = processor;this.number = number;}@Overridepublic void execute() {this.processor.substract(number);}@Overridepublic void undo() {this.processor.add(number);}}

Processor 计算器的处理器(接收者)

/*** 计算器的处理器,真正实现运算操作*/
@Data
public class Processor {/*** 记录运算的结果*/private int result;public void add(int num){//实现加法功能result += num;}public void substract(int num){//实现减法功能result -= num;}
}

Calculator 计算器(调用者)

/*** 计算器类,上边有加法、减法、撤销和恢复按钮*/
@Data
public class Calculator {/*** 操作的命令的记录,撤销时用*/private List<Command> undoCmds = new ArrayList();/*** 撤销的命令的记录,恢复时用*/private List<Command> redoCmds = new ArrayList();private Command addCommand = null;private Command substractCommand = null;/*** 执行加法操作*/public void addPressed(){this.addCommand.execute();//把操作记录到历史记录里面undoCmds.add(this.addCommand);}/*** 执行减法操作*/public void substractPressed(){this.substractCommand.execute();//把操作记录到历史记录里面undoCmds.add(substractCommand);}/*** 撤销一步操作*/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("很抱歉,没有可恢复的命令");}}
}

以上,一个支持加、减、撤销和恢复操作的计算器已经设计好了,下边我们来模拟一下使用。

public static void main(String[] args) {// 创建接收者,就是我们的计算机的处理器Processor processor = new Processor();// 创建计算器对象Calculator calculator = new Calculator();System.out.println("爸爸:小明,过来帮爸爸算算今天赚了多少钱");System.out.println("小明:来了,爸爸你说吧");System.out.println("爸爸:白菜卖了20块");AddCommand addCommand = new AddCommand(processor,20);calculator.setAddCommand(addCommand);calculator.addPressed();System.out.println("小明:卖白菜的钱:"+processor.getResult());System.out.println("爸爸:萝卜卖了15块");addCommand = new AddCommand(processor,15);calculator.setAddCommand(addCommand);calculator.addPressed();System.out.println("小明:加上卖萝卜的钱:"+processor.getResult());System.out.println("买了一包烟,花了5块");SubtractCommand subtractCommand = new SubtractCommand(processor, 5);calculator.setSubstractCommand(subtractCommand);calculator.substractPressed();System.out.println("小明:减去买烟的钱:"+processor.getResult());System.out.println("爸爸:不对好像算错了,重来");calculator.undoPressed();System.out.println("小明:撤销一次后:"+processor.getResult());calculator.undoPressed();System.out.println("小明:撤销两次后:"+processor.getResult());calculator.undoPressed();System.out.println("小明:撤销三次后:"+processor.getResult());System.out.println("爸爸:哈哈~好像白菜和萝卜没算错,烟这个是私房钱买的,别算进去了");calculator.redoPressed();System.out.println("小明:恢复一次操作:"+processor.getResult());calculator.redoPressed();System.out.println("小明:恢复两次操作:"+processor.getResult());}

执行main方法后,得到下边的输出:

爸爸:小明,过来帮爸爸算算今天赚了多少钱
小明:来了,爸爸你说吧
爸爸:白菜卖了20块
小明:卖白菜的钱:20
爸爸:萝卜卖了15块
小明:加上卖萝卜的钱:35
买了一包烟,花了5块
小明:减去买烟的钱:30
爸爸:不对好像算错了,重来
小明:撤销一次后:35
小明:撤销两次后:20
小明:撤销三次后:0
爸爸:哈哈~好像白菜和萝卜没算错,烟这个是私房钱买的,别算进去了
小明:恢复一次操作:20
小明:恢复两次操作:35

2、存储恢复式

还是这个计算器,我们来改动一下,让它变成通过存储恢复的方式进行撤销和恢复撤销。我们只需要让每个命令在执行之前,先记住此时的值,恢复时,只需要将这个命令记住的值恢复就好了。

其它不变,改动一下:AddCommand(加法命令)和 SubtractCommand(减法命令)
1、 定义一个记录之前值的属性 previousValue ;2、 undo方法改为重新set一下计算结果为之前记录的值。

/*** 加法命令*/
public class AddCommand implements Command{/*** 持有真正进行运算操作的对象*/private Processor processor;/*** 记录之前的值*/private int previousValue;/*** 要加上的数值*/private int number;public AddCommand(Processor processor, int number) {this.processor = processor;this.number = number;}@Overridepublic void execute() {this.previousValue = this.processor.getResult();this.processor.add(number);}@Overridepublic void undo() {this.processor.setResult(this.previousValue);}}
/*** 减法命令*/
public class SubtractCommand implements Command{/*** 持有真正进行运算操作的对象*/private Processor processor;/*** 记录之前的值*/private int previousValue;/*** 要减去的数值*/private int number;public SubtractCommand(Processor processor, int number) {this.processor = processor;this.number = number;}@Overridepublic void execute() {this.previousValue = this.processor.getResult();this.processor.substract(number);}@Overridepublic void undo() {this.processor.setResult(this.previousValue);}}

我们可以将undo方法提取出来到一个抽象类中,这里为了简单就没提取。再执行一下main方法,我们就可以看到输出的结果是一样的。

六、命令模式扩展 – 队列请求

  对命令对象进行排队,组成工作队列,然后依次取出命令对象来执行。通常用多线程或线程池来进行命令队列的处理。

七、命令模式扩展 – 日志请求

  日志请求,就是把请求保存下来,一般是采用持久化存储的方式。这样,如果在运行请求的过程中,系统崩溃了,系统重新启动时,就可以从保存的历史记录中,获取日志请求,并重新执行。
  Java中实现日志请求,一般就是将对象序列化保存起来,使用时,进行反序列化操作。

八、命令模式的优点

优点:

  • 更松散的耦合。 命令模式使得发起命令的对象和真正实现命令的对象完全解耦。
  • 更好的扩展性 由于命令发起对象和实现对象完全解耦,因此我们可以很容易的扩展新的命令,只需要实现新的命令并进行装配,新命令就可以使用了。这个过程中,已有的实现完全不需要变化。
  • 额外的功能 命令模式还可以提供诸如宏命令、队列请求、日志请求等灵活的操作。

九、命令模式的应用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 在需要支持撤销和恢复撤销的地方,如GUI、文本编辑器等。
  • 需要用到日志请求、队列请求的地方。
  • 在需要事物的系统中。命令模式提供了对事物进行建模的方法,命令模式有一个别名就是Transaction。

http://chatgpt.dhexx.cn/article/5XE2qhXa.shtml

相关文章

【每天一个java设计模式(十五)】 - 命令模式

命令模式是一种数据驱动的设计模式&#xff0c;它属于行为型模式。请求以命令的形式包裹在对象中&#xff0c;并传给调用对象。调用对象寻找可以处理该命令的合适的对象&#xff0c;并把该命令传给相应的对象&#xff0c;该对象执行命令。 命令模式也就是一个用户发送请求&…

码农小汪-设计模式之-命令模式

大话设计模式的例子讲的非常的好&#xff0c;理解起来也方便&#xff01;有时候忘了。想到这些特殊的例子感觉就是特别爽。 烤羊肉串带来的思考&#xff01; 路边摊羊肉串&#xff1a; 老板&#xff0c;我这里排的比较先啊&#xff0c;我最先给钱。老板这个没有熟啊。我的是…

命令模式(行为型)

一、什么是命令式 命令(Command)模式又叫作动作(Action)模式或事务(Transaction)模式&#xff0c;是一种对象的行为模式。将一个请求封装为一个对象&#xff0c;从而使你可用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以及支持可撤消的操作…

命令模式---电视机遥控器

电视机是请求的接收者&#xff0c;遥控器是请求的发送者&#xff0c;遥控器上有一些按钮&#xff0c;不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演&#xff0c;有三个具体的命令类实现了抽象命令接口&#xff0c;这三个具体命令类分别代表三种操作&#…

C++设计模式-命令模式

目录 基本概念 代码与实例 基本概念 命令模式&#xff08;Command&#xff09;&#xff0c;将一个请求封装为对象&#xff0c;从而使你看用不同的请求对客户端进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以及支持可撤销操作。 命令模式的作用&#xff1a; …

设计模式---命令模式

命令模式 命令模式的定义 ​ 命令模式是一个高内聚的模式&#xff0c;其定义为&#xff1a;Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.&#xff08;将一…

【设计模式】命令模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )

文章目录 一、命令模式简介二、命令模式 适用场景三、命令模式 优缺点四、命令模式 与 备忘录模式五、命令模式 代码示例1、命令接口2、发布命令类3、关闭命令类4、游戏类5、命令执行者类6、测试类 一、命令模式简介 命令模式 : 将 不同的请求 封装成 不同的请求对象 , 以便 使…

设计模式(16)命令模式

**定义&#xff1a;**将一个请求封装成一个对象&#xff0c;从而让你使用不同的请求把客户端参数化&#xff0c;对请求排队或者记录请求日志&#xff0c;可以提供命令的撤销和恢复功能。 **类型&#xff1a;**行为类模式 类图&#xff1a; 命令模式的结构 ​ 顾名思义&#…

什么是命令模式?

一、命令模式的定义 命令是对命令的封装&#xff0c;每一个命令都是一个操作&#xff0c;请求方发出请求&#xff0c;接收方接收请求&#xff0c;并执行操作。命令模式解耦了请求方和接收方&#xff0c;命令模式属于行为型模式 二、命令模式的uml图和通用写法 uml 通用写法 …

设计模式之命令模式详解

1 概述 日常生活中&#xff0c;我们出去吃饭都会遇到下面的场景。我们可以将女招待理解成一个请求的发送者&#xff0c;用户通过它来发送一个“点餐”请求&#xff0c;而厨师是“点餐”请求的最终接收者和处理者&#xff0c;在图中&#xff0c;顾客和厨师之间并不存在直接耦合…

命令模式

一、命令模式介绍 在软件设计中&#xff0c;我们经常需要向某些对象发送请求&#xff0c;但是并不知道请求的接收者是谁&#xff0c;也不知道被请求的操作是哪个。我们只需要在程序运行时指定具体的请求接收者即可&#xff0c;此时可以使用命令模式来设计。 命令模式使得请求发…

Java设计模式——命令模式

文章目录 命令模式 命令模式 命令模式很好理解&#xff0c;举个例子&#xff0c;司令员下令让士兵去干件事情&#xff0c;从整个事情的角度来考虑&#xff0c;司令员的作用是&#xff0c;发出口令&#xff0c;口令经过传递&#xff0c;传到了士兵耳朵里&#xff0c;士兵去执行…

如何设置IPv4和IPv6报文的DSCP值——网络测试仪实操

一、操作说明 在QoS测试中&#xff0c;经常要设置不同优先级的报文&#xff0c;来验证被测设备对于优先级的调度。所以&#xff0c;我们就要了解如何设置IPv6和IPv6报文中的DSCP&#xff08;大部分使用DSCP值&#xff0c;也会用到TOS值&#xff09; 这里我们使用测试接交换机&…

DSCP vs IPv4 Tos

首先看IPv4包头如下 其中&#xff0c;Qos用到的是Tos定义有下面两种&#xff1a; 老的IPv4 TOS Byte定义和值 新的DSCP定义和值 DSCP值 DSCP ValueMeaningDrop ProbabilityEquivalent IP Precedence Value101 110 (46)High Priority Expedited Forwarding (EF)N/A101 – …

c语言socket设置IPV4/6的dscp值

环境&#xff1a;linux centos7 、x86 、UDP包 使用sock需要增加头文件 #include <sys/socket.h> #include <sys/types.h> 设置方法很简单&#xff0c;都是使用setsockopt函数&#xff0c;就是找资料及如何太麻烦&#xff0c;尤其是IPV6。需要注意IPV4设置的是I…

tos cos dscp 区别和作用

tos cos 和dscp 都是通过iptable 的mange 的mark 标签来更改的。 谈到qos首先需要了解qos调度的几个重要过程,qos调度过程包括网络入口数据流量的分类和标记、骨干网设备上的拥塞避免和拥塞管理、网路出口的队列调度这几个重要过程. 1、cos和tos的区别: 通过acl对流量进行分类以…

IP优先级和DSCP之间的关系

1. IP优先级和DSCP之间的关系 DiffServ体系定义的DS字段&#xff0c;取代IPv4中ToS字段作出有关数据包分类和流量调节功能的策略。 1.1. ToS字段 在IPv4的报文头中&#xff0c;TOS字段是1字节&#xff0c;根据RFC1122的定义&#xff0c;IP优先级&#xff08;IPPrecedence&…

802.1P优先级、IP优先级、TOS优先级及DSCP优先级的分类和对应

1、802.1P优先级&#xff08;有时也称COS优先级&#xff09;&#xff1a; 802.1p用户优先级定义在二层802.1Q 标签头中的TCI字段中。&#xff0c;和VLAN ID一起使用&#xff0c;位于高位起16-18bit字段&#xff0c;长度3bit&#xff0c;取值范围0-7&#xff0c;0优先级最低&…

DSCP 与IP 优先级IP优先级

首先看IPv4包头如下 其中&#xff0c;Qos用到的是Tos定义有下面两种&#xff1a; 老的IPv4 TOS Byte定义和值 新的DSCP定义和值 DSCP值 DSCP Value Meaning Drop Probability Equivalent IP Precedence Value 101 110 (46) High Priority Expedited Forwarding (EF) N/A…

IP Precedence、DSCP、TOS

刚开始接触QoS时&#xff0c;经常会被IP Precedence、DSCP、TOS这些名词搞迷糊&#xff0c;那么接下来就梳理一下。 首先 IP Precedence IPv4中有8bit作为TOS字段&#xff0c;一开始RFC791定义了TOS前三位为IP Precedence&#xff0c;划分了8个优先级&#xff0c;可用于流分类…