C# 设计模式之命令模式
总目录
前言
命令模式在日常中,也是比较常见的,就比如:妈妈和爸爸说,你去让孩子把地扫一下;这就是是一个命令,命令中的 下达命令的是妈妈,传达命令的是爸爸,接受命令做事的是孩子;那为什么还要爸爸传达呢?直接去让孩子做事不是更直接,这个好回答,就是因为妈妈省时省力啊!
1 基础介绍
- 定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户(客户程序,也是行为的请求者)进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式属于对象的行为型模式。命令模式是把一个操作或者行为抽象为一个对象,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。
- 命令模式,顾名思义就是将命令抽象化,然后将请求者和接收者通过命令进行绑定。而命令的请求者只管下达命令,命令的接收者只管执行命令。从而实现了解耦,请求者和接受者二者相对独立。
- 命令模式通过加入命令请求者角色来实现 将发出命令的责任和执行命令的责任分割开,并协助发出命令者来传达命令,使得执行命令的接收者可以收到命令并执行命令。
- 命令模式的目的是解除命令发出者和接收者之间的紧密耦合关系,使二者相对独立,有利于程序的并行开发和代码的维护。
- 命令模式中的角色:
-
客户角色(Client):创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。(主要是负责将执行者和命令类进行绑定,其次将命令类和调用者进行绑定。使其调用者可以通过命令类给执行者传递消息进行执行。)
-
命令角色(Command):声明了一个给所有具体命令类实现的抽象接口。
-
具体命令角色(ConcreteCommand):命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
-
请求者角色(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
-
接受者角色(Receiver):接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
-
2 使用场景
(1)、系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法把命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。
(2)、系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。
(3)、如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。
(4)、系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。
3 实现方式
下面我们以一个案例,循序渐进的来了解命令模式。
案例:有了小家庭里,有妈妈和爸爸,还有两个孩子,一个叫童童,一个叫睦睦
现在 妈妈准备做饭了,家里没有了,让 童童 去买瓶油回来!
1. 娃儿买东西V1.0
需求:妈妈让 童童 买油
public class TongTong{public void BuyOil(){Console.WriteLine("童童买好油了!");}}
客户端就相当于妈妈的角色
static void Main(string[] args){TongTong tt = new TongTong();tt.BuyOil();Console.ReadKey();}
需求变了,现在妈妈发现家里没有可乐了,让睦睦 去买可乐
public class MuMu{public void BuyCola(){Console.WriteLine("睦睦买好可乐了!");}}
现在我们需求变了,我们新增了一个类,并且还需将客户端的代码改一下
static void Main(string[] args){TongTong tt = new TongTong();tt.BuyOil();MuMu mm = new MuMu();mm.BuyCola();Console.ReadKey();}
我们发现只要我们需求发生一点改变,就需要改动代码,并且客户端直接调用实现类,耦合度太高了,这个违背了依赖倒置和开闭原则。
2. 娃儿买东西V2.0
我们发现在这个事件中,妈妈不管叫 童童还是睦睦 去买东西,去买东西的人和买的物品会发生变化。
不变的是买东西 这个命令是 他们都需要遵守的,因此我们将命令封装起来。
//定义一个命令的抽象类public abstract class AbstractCommand{//规定实现类必须实现的方法//该方法表示 执行 具体的命令public abstract void Execute();}public class TongTongCommand : AbstractCommand{private TongTong _TongTong;public TongTongCommand(TongTong tong){_TongTong = tong;}public override void Execute(){_TongTong.BuyOil();}}public class MuMuCommand : AbstractCommand{private MuMu _MuMu;public MuMuCommand(MuMu muMu){_MuMu = muMu;}public override void Execute(){_MuMu.BuyCola();}}
客户端就相当于妈妈的角色,此时我们需要实现让童童买油,让睦睦买可乐的需求,可以这样实现。
static void Main(string[] args){//童童买水TongTong tongTong = new TongTong();AbstractCommand command = new TongTongCommand(tongTong);command.Execute();//睦睦买可乐MuMu muMu = new MuMu();command = new MuMuCommand(muMu);command.Execute();Console.ReadKey();}
通过将命令定义成抽象类,规定实现类必须实现的执行命令的方法,我们实现了不同命令的解耦。
如果此时妈妈 需要童童 去买水,我们可以在TongTong 类中 新增了BuyWater的方法,然后修改TongTongCommand类即可。
3. 娃儿买东西V3.0
但是还有一个问题,每次要买点啥,都得妈妈亲自过来和童童或睦睦说,太浪费时间了,于是和一旁帮厨的爸爸说,一会儿我缺什么要买的,我就给你说下,至于你叫谁去我不管了,给我买回来就行!
public class BaBa{private AbstractCommand _AbstractCommand;public BaBa(AbstractCommand command){_AbstractCommand = command;}//告知相关的命令public void Invoke() {_AbstractCommand.Execute();}}
此时于爸爸而言,不管执行,只管将妈妈的命令传达出去,你们命令中对应的人去做该做的事情就行。
此时与妈妈而言,不管过程,也不管谁去执行,只管告诉爸爸 一个命令(需要谁去做什么)就行。
与孩子而言,只管执行!
客户端调用:
static void Main(string[] args){var tongTong = new TongTong();var command = new TongTongCommand(tongTong);BaBa baBa = new BaBa(command);baBa.Invoke();Console.ReadKey();}
4. 代码汇总
public class TongTong{public void BuyOil(){Console.WriteLine("童童买好油了!");}}public class MuMu{public void BuyCola(){Console.WriteLine("睦睦买好可乐了!");}}//定义一个命令的抽象类public abstract class AbstractCommand{//规定实现类必须实现的方法//该方法表示 执行 具体的命令public abstract void Execute();}public class TongTongCommand : AbstractCommand{private TongTong _TongTong;public TongTongCommand(TongTong tong){_TongTong = tong;}public override void Execute(){_TongTong.BuyOil();}}public class MuMuCommand : AbstractCommand{private MuMu _MuMu;public MuMuCommand(MuMu muMu){_MuMu = muMu;}public override void Execute(){_MuMu.BuyCola();}}public class BaBa{private AbstractCommand _AbstractCommand;public BaBa(AbstractCommand command){_AbstractCommand = command;}//告知相关的命令public void Invoke() {_AbstractCommand.Execute();}}
客户端调用:
static void Main(string[] args){var tongTong = new TongTong();var command = new TongTongCommand(tongTong);BaBa baBa = new BaBa(command);baBa.Invoke();Console.ReadKey();}
后续有什么命令,那就新增一个命令实现类即可,实现了命令发起者 与 接收者之间的解耦。
4 优缺点分析
-
优点:
- 命令模式使得新的命令很容易被加入到系统里。
- 可以设计一个命令队列来实现对请求的Undo和Redo操作。
- 可以较容易地将命令写入日志。
- 可以把命令对象聚合在一起,合成为合成命令。合成命令式合成模式的应用。
- 降低了系统的耦合性。将调用操作对象和知道如何实现该操作对象的解耦。
-
缺点:
- 使用命令模式可能会导致系统有过多的具体命令类。这会使得命令模式在这样的系统里变得不实际。
结语
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
C#设计模式之十四命令模式(Command Pattern)【行为型】
C#设计模式(15)——命令模式(Command Pattern)
c#中命令模式详解
相关文章:
C# 设计模式之命令模式
总目录 前言 命令模式在日常中,也是比较常见的,就比如:妈妈和爸爸说,你去让孩子把地扫一下;这就是是一个命令,命令中的 下达命令的是妈妈,传达命令的是爸爸,接受命令做事的是孩子&a…...
pod详解 list-watch机制 预选优选策略 如何指定节点调度pod
K8S是通过 list-watch 机制实现每个组件的协同工作 controller-manager、scheduler、kubelet 通过 list-watch 机制监听 apiserver 发出的事件,apiserver 也会监听 etcd 发出的事件 scheduler的调度策略: 预选策略(Predicates)…...
深入探索:【人工智能】、【机器学习】与【深度学习】的全景视觉之旅
目录 第一部分:人工智能、机器学习与深度学习概述 1.1 人工智能的概念与发展 代码示例:简单的AI决策系统 1.2 机器学习的定义与分类 代码示例:简单的线性回归模型 1.3 深度学习的基础与应用 代码示例:构建简单的神经网络 …...
使用js和css 实现div旋转围绕圆分布排列
记录,以防忘记 围绕圆 import React, { useEffect } from react; import ./index.scoped.scss;const Test () > {const arr Array.from({ length: 28 }, (_, index) > index 1);useEffect(() > {const dayTotal arr.length;// 动态设置每个点的旋转角…...
SQL Server中CPU使用率过高的排查
CPU使用率过高有许多可能原因,但以下原因最为常见: 1.由于以下情况,表或索引扫描导致的高逻辑读取: 过期统计信息 缺少索引 参数敏感计划 (PSP) 问题 设计不佳的查询 2.工作负荷增加 对于安装了sqlserver的服务器,可…...
AUTOSAR AP常用文档前缀
AUTOSAR AP常用文档前缀总结如下表: 缩写全称含义EXPExplanation文档类别,跟踪类别 讨论其他文件中已经显示的内容的说明材料MODModel文档类别,跟踪类别 元级别1(模型)上的建模内容(模型或从模型生成的内容)RSRequirement Specification文档…...
服务器迁移基于Tomcat部署的java应用,没有源码怎么办?
文章目录 反编译创建java工程编译新的数据库配置类DbUtilclass文件替换到Tomcat配置的应用路径 docBase背景:非国产化项目服务器审计不通过,需要迁移到外部公司。由于项目是第三方公司开发,丢失java项目源码。 部署环境:Tomcat7,JDK1.8 涉及JAVA项目的有两个服务,一个电台…...
kafka-go使用:以及kafka一些基本概念说明
关于kafka 作为开发人员kafka中最常关注的几个概念,是topic,partition和group这几个概念。topic是主题的意思,简单的说topic是数据主题,这样解释好像显得很苍白,只是做了个翻译。一图胜前言,我们还是通过图解来说明。…...
景联文科技:破解数据标注行业痛点,引领高质量AI数据服务
数据标注行业是人工智能和机器学习领域中一个非常重要的组成部分。随着AI技术的发展,对高质量标注数据的需求也在不断增长。 数据标注市场的痛点 1. 团队管理 在众包和转包模式下,管理大量的标注人员是一项挑战。 需要确保标注人员的专业性、稳定性和…...
C#获取Network的相关信息
1,获取网络的通断。 //方法1:无效果,并不能反映当前网络通断 bool availableSystem.Windows.Forms.SystemInformation.Network//方法2:通过VB获取网络状态,可反映当前网络通断 Microsoft.VisualBasic.Devices.Network…...
Jenkins 部署Vue项目指引: Vue项目本地跨域代理 、解决ERR_UNSAFE_PORT
文章目录 引言I Jenkins 部署Vue项目配置插件安装系统配置NodeJS安装目录和别名设置新建任务(通用类型)构建环境Build Steps(构建步骤)II nginx部署站点(端口和站点目录的映射)查找Nginx配置文件端口和站点目录的映射III Vue项目本地跨域代理,屏蔽掉后端服务API的网关IP…...
C语言电子画板
目录 开头程序程序的流程图程序的效果结尾 开头 大家好,我叫这是我58。今天,我们来看一下我用C语言编译的电子画板和与之相关的一些东西。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <Windows.h> int main() …...
Android Gradle开发与应用技术原理
Android Gradle开发与应用技术原理 Android Gradle开发与应用技术原理一、概述二、Gradle构建原理1. Gradle架构2. Gradle构建过程3. 构建脚本 三、Gradle插件机制四、在Android应用中实现Text-to-Speech(TTS)功能1. 配置Gradle依赖2. 实现TTS功能示例代…...
Midjourney入门-提示词基础撰写与公式
前言 在前几篇教程里我们已经可以初步使用Midjourney进行出图了。 包括也了解了Midjourney的指令与参数。 但如果你想用Midjourney去生成各种各样高质量的图片, 并且生成的图片是你想要的画面内容,也就是更好控制生成图片的画面内容与风格…...
Apache Tomcat服务器版本号隐藏
渗透测试时发现有一台服务器的404报错页面中,有Apache Tomcat的版本号信息显示,发生了信息泄露,可能导致服务器被攻击。如下所示: 解决步骤如下: 1. 隐藏HTTP响应头中的Server信息 Tomcat默认会在HTTP响应头中包含S…...
【Qt】Qt编程注意事项
目录 Qr中的命名规范 Qt Creator中的快捷键 查询文档的方式 Qt窗口坐标体系 Qr中的命名规范 在学习编程语言阶段,给变量、函数、文件、类命名是非常有讲究的。 命名要有描述性,不要使用abc,xyz这种比较无规律的名字类描述。如果名字比较…...
在Linux系统安装Kafka
注意:我的是在云服务器上基于Docker配 在防火墙上放行端口号 2181(Zookeeper) 9092(Kafka) 一、先配置 Docker 守护进程(daemon)的镜像加速器(registry mirrors) sudo mkdir -p /etc/docker sudo tee /etc/docker/da…...
【CSharp】简单定义一个异步方法
【CSharp】定义一个异步方法 1.背景2.异步方法3.代码说明1.背景 相关博客: 【CSharp】使用异步事件处理程序和委托来进行异步调用 https://blog.csdn.net/jn10010537/article/details/140898179在 C# 中,异步方法和同步方法是两种执行代码的方式, 它们主要区别在于处理任务…...
贪心算法之货仓选址问题
#include<stdio.h> #include<stdlib.h> #include<math.h>//贪心算法之货仓选址问题/*** void* p是万能指针,可以和其它任意类型的指针进行转换,前提是确保转换是合法的*/ //写好用于qsort的比较函数,这里写的函数一般用于…...
Java网络编程——Request Response 对象
Response - 网页 上一章我们学习了 Java 中使用 Okhttp3 库请求网页或调用 API 的知识。 使用一条语句执行调用请求,并取得返回结果字符串: call.execute().body().string()execute() 方法是真正执行发送请求,前面的一系列代码是做前置准备…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
