设计模式之命令模式详解

article/2025/10/20 17:13:13

1 概述

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

在这里插入图片描述

在软件开发中也存在很多类似的请求发送者和接收者对象,例如一个按钮,它可能是一个“关闭窗口”请求的发送者,而按钮点击事件处理类则是该请求的接收者。为了降低系统的耦合度,将请求的发送者和接收者解耦,我们可以使用一种被称之为命令模式的设计模式来设计系统,在命令模式中,发送者与接收者之间引入了新的命令对象,将发送者的请求封装在命令对象中,再通过命令对象来调用接收者的方法。

在软件开发中,我们经常需要向某些对象发送请求(调用其中的某个或某些方法),但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,此时,我们特别希望能够以一种松耦合的方式来设计软件,使得请求发送者与请求接收者能够消除彼此之间的耦合,让对象之间的调用关系更加灵活,可以灵活地指定请求接收者以及被请求的操作。

命令模式定义

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,解耦合。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。

命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。

命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。

注:命令模式无法解决类的个数增加的问题

6.3.2 结构

命令模式包含以下主要角色:

  • 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

6.3.3 案例实现

将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。

服务员: 就是调用者角色,由她来发起命令。

资深大厨: 就是接收者角色,真正命令执行的对象。

订单: 命令中包含订单。

类图如下:

在这里插入图片描述

代码如下:

public interface Command {void execute();//只需要定义一个统一的执行方法
}public class OrderCommand implements Command {//持有接受者对象private SeniorChef receiver;private Order order;public OrderCommand(SeniorChef receiver, Order order){this.receiver = receiver;this.order = order;}public void execute()  {System.out.println(order.getDiningTable() + "桌的订单:");Set<String> keys = order.getFoodDic().keySet();for (String key : keys) {receiver.makeFood(order.getFoodDic().get(key),key);}try {Thread.sleep(100);//停顿一下 模拟做饭的过程} catch (InterruptedException e) {e.printStackTrace();}System.out.println(order.getDiningTable() + "桌的饭弄好了");}
}public class Order {// 餐桌号码private int diningTable;// 用来存储餐名并记录份数private Map<String, Integer> foodDic = new HashMap<String, Integer>();public int getDiningTable() {return diningTable;}public void setDiningTable(int diningTable) {this.diningTable = diningTable;}public Map<String, Integer> getFoodDic() {return foodDic;}public void setFoodDic(String name, int num) {foodDic.put(name,num);}
}// 资深大厨类 是命令的Receiver
public class SeniorChef {public void makeFood(int num,String foodName) {System.out.println(num + "份" + foodName);}
}public class Waitor {private ArrayList<Command> commands;//可以持有很多的命令对象public Waitor() {commands = new ArrayList();}public void setCommand(Command cmd){commands.add(cmd);}// 发出命令 喊 订单来了,厨师开始执行public void orderUp() {System.out.println("美女服务员:叮咚,大厨,新订单来了.......");for (int i = 0; i < commands.size(); i++) {Command cmd = commands.get(i);if (cmd != null) {cmd.execute();}}}
}public class Client {public static void main(String[] args) {//创建2个orderOrder order1 = new Order();order1.setDiningTable(1);order1.getFoodDic().put("西红柿鸡蛋面",1);order1.getFoodDic().put("小杯可乐",2);Order order2 = new Order();order2.setDiningTable(3);order2.getFoodDic().put("尖椒肉丝盖饭",1);order2.getFoodDic().put("小杯雪碧",1);//创建接收者SeniorChef receiver=new SeniorChef();//将订单和接收者封装成命令对象OrderCommand cmd1 = new OrderCommand(receiver, order1);OrderCommand cmd2 = new OrderCommand(receiver, order2);//创建调用者 waitorWaitor invoker = new Waitor();invoker.setCommand(cmd1);invoker.setCommand(cmd2);//将订单带到柜台 并向厨师喊 订单来了invoker.orderUp();}
}

6.3.4 优缺点

1,优点:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

2,缺点:

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
  • 系统结构更加复杂。

6.3.5 使用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

6.3.6 JDK源码解析

Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法

//命令接口(抽象命令角色)
public interface Runnable {public abstract void run();
}//调用者
public class Thread implements Runnable {private Runnable target;public synchronized void start() {if (threadStatus != 0)throw new IllegalThreadStateException();group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {}}}private native void start0();
}

会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。

/*** jdk Runnable 命令模式*		TurnOffThread : 属于具体*/
public class TurnOffThread implements Runnable{private Receiver receiver;public TurnOffThread(Receiver receiver) {this.receiver = receiver;}public void run() {receiver.turnOFF();}
}
/*** 测试类*/
public class Demo {public static void main(String[] args) {Receiver receiver = new Receiver();TurnOffThread turnOffThread = new TurnOffThread(receiver);Thread thread = new Thread(turnOffThread);thread.start();}
}

http://chatgpt.dhexx.cn/article/vhH9Fuxb.shtml

相关文章

命令模式

一、命令模式介绍 在软件设计中&#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;可用于流分类…

【网络】Cos和ToS和DSCP|Qos|PHB的含义和区别以及映射

目录 视频教程&#xff1a; 介绍和区别 Qos/Cos IP-TOS&#xff08;IPP/CS&#xff09;和DSCP PHB&#xff08;Per-Hop-Behaviors&#xff09; 区别 各个等级的DSCP值和含义(PHB) 映射 COS到DSCP的映射 IP-Precedence到DSCP的映射&#xff08;Tos-->DSCP&#xff09…

TOS 和DSCP

IPv4报文中有三种承载QoS优先级标签的方式&#xff0c;分别为基于二层的CoS字段&#xff08;IEEE802.1p&#xff09;的优先级、基于IP层的IP优先级&#xff08;IP Precedence&#xff09;字段ToS优先级和基于IP层的DSCP&#xff08;Differentiated Services Codepoint&#xff…

什么是DSCP,如何使用DSCP标记搭配ROS策略

什么是DSCP&#xff0c;如何使用DSCP标记搭配ROS策略 一、什么是DSCP DSCP&#xff1a;差分服务代码点&#xff08;Differentiated Services Code Point&#xff09;&#xff0c;IETF于1998年12月发布了Diff-Serv&#xff08;Differentiated Service&#xff09;的QoS分类标准…

TOS 和 DSCP理解

背景 IPv4报文中有三种承载QoS优先级标签的方式&#xff0c;分别为基于二层的CoS字段&#xff08;IEEE802.1p&#xff09;的优先级、基于IP层的IP优先级&#xff08;IP Precedence&#xff09;字段ToS优先级和基于IP层的DSCP&#xff08;Differentiated Services Codepoint&am…

谈谈ES5和ES6的区别

我们都知道JavaScript是由三部分组成&#xff1a; 1. ECMAScript(核心)&#xff1a;规定了语言的组成部分>语法、类型、语句、关键字、保留字、操作符、对象 2. BOM(浏览器对象模型): 支持访问和操作浏览器窗口&#xff0c;可以控制浏览器显示页面以外的部分。 3. DOM(文…

ES5基础语法

一.类与对象 class father {that this;constructor(uname, age) {this.uname uname;this.age age;}sing(song) {console.log(this.uname song);}}class son extends father {constructor(uname,age) {super(uname,age);this.unameuname;this.age age;}sing(song){console.…

ES5语法

从今天起&#xff0c;我们开始接触JS部分&#xff0c;先从ES5一些简单的语法入手。下面先看下思维导图&#xff0c;确定我们的学习思路&#xff1a; 今天我们先学习代码规范&#xff0c;数据类型以及数据类型转换三个模块 代码规范 JS中的一切都区分大小写 标识符&#xff1…

es5 es6 互相转换

- 工具&#xff1a;Visual Studio Code - 具体过程 1.新建项目&#xff0c;dist存放ES6格式的&#xff0c;src存放ES5格式的&#xff0c;如下 index.html内容&#xff0c;此处引用dist中的js 2.打开终端&#xff08;ctrl &#xff09; npm需要安装&#xff0c;安装node即可…

套接字

套接字&#xff08;socket&#xff09;最早是由BSD在1982年引入的通信机制&#xff0c;目前已被广泛移植到主流的操作系统中。对于应用开发人员来说&#xff0c;套接字&#xff08;socket&#xff09;是一种特殊的I/O接口&#xff0c;也是一种文件描述符。socket是一种常用的进…

原始套接字简介

一 原始套接字概述 原始套接字&#xff0c;指在传输层下面使用的套接字。流式套接字和数据报套接字这两种套接字工作在传输层&#xff0c;主要为应用层的应用程序提供服务&#xff0c;并且在接收和发送时只能操作数据部分&#xff0c;而不能对IP首部或TCP和UDP首部进行操作&am…