《Head First设计模式》读书笔记 —— 命令模式

文章目录

    • 本节用例
    • 餐厅类比
      • 点餐流程
      • 角色与职责
      • 从餐厅到命令模式
    • 命令模式
      • 第一个命令对象
        • 实现命令接口
        • 实现一个命令
      • 使用命令对象
      • NoCommand与空对象
    • 定义命令模式
    • 支持撤销功能
      • 使用状态实现撤销
      • 多层次撤销
    • One One One …… more things
      • 宏命令
        • 使用宏命令
      • 队列请求
      • 日志请求
    • 总结

《Head First设计模式》读书笔记
相关代码:Vks-Feng/HeadFirstDesignPatternNotes: Head First设计模式读书笔记及相关代码

将封装带到一个全新的境界:把方法调用(method invoke)封装起来

本节用例

设计一个家电自动化遥控器的API。

  • 遥控器有7个可编程的插槽
  • 每个插槽都有对应的开关按钮
  • 具备一个整体的撤销按钮

希望创建一组控制遥控器的API,让每个插槽都能控制一个或一组装置。且需要能够控制目前的装置和任何未来可能出现的装置

家电设计:种类众多,接口不一,以后还会有更多厂商,每个类还会有其他各种方法
家电类设计

所以分离的关注点:遥控器应该知道如何解读按钮被按下的动作,然后发出正确的请求,但是遥控器不需要知道这些家电自动化的细节

命令模式将“动作的请求者”从“动作的执行者”对象中解耦

  • 此例中:遥控器是请求者、厂商类是执行者

采用“命令对象”,把请求封装成一个特定对象,请求发出时就可以让命令对象做相关工作

  • 此例中:每个按钮都存储一个命令对象,当按钮按下时,命令对象做相关的工作。遥控器不需要知道工作内容是什么,只要命令对象能和正确的对象沟通并完成任务即可。实现了遥控器和具体家电的解耦

餐厅类比

点餐流程

  1. 顾客将订单交给招待员createOrder()
  2. 招待员拿走订单takeOrder(),将订单传递给订单柜台并发出通知orderUp()
  3. 厨师根据订单备餐cook()

角色与职责

订单:封装了准备餐点的请求

  • 订单可以被传递
  • 订单只包含一个方法orderUp(),封装了备餐动作
  • 订单内有一个到“需要进行准备工作的对象”的引用(即厨师)
    招待:接受订单,调用订单的orderUp()方法
  • 招待接收不同用户的不同订单,其takeOrder()被传入不同参数
  • 招待知道订单包含orderUp()方法,在需要备餐时调用即可
  • 招待无需知道订单内容、谁来备餐,只需知道并调用orderUp()
    厨师:具备准备餐点的只是
  • 真正知道如何备餐
  • orderUp()被调用时,厨师接手,实现需要创建餐点的所有方法
  • 厨师与招待彻底解耦

从餐厅到命令模式

  1. 客户创建一个命令对象
  2. 客户利用setCommand()将命令对象存储在调用者中
  3. 客户要求调用者执行命令
    命令模式流程
  • 客户(Client)负责创建命令对象createCommandObject()
  • 命令对象包含了接收者上的一组动作,它提供了一个execute()方法,封装了这些动作。
    • 动作和接收者在命令对象中被绑在一起receiver.action()
    • 调用execute()会调用接收者的这些动作
  • 客户在调用者对象(Invoker)上调用setCommand()方法,并把它传入命令对象。命令对象被存储其中并在之后被使用
  • 某个时刻,调用者将调用命令对象

命令模式

第一个命令对象

实现命令接口
public interface Command {public void execute();
}
实现一个命令

以打开电灯命令为例

public class Light {  public void on() {  System.out.println("Light is On");  }  
}
public class LightOnCommand implements Command{  Light light;  public LightOnCommand(Light light) {  this.light = light;  }  public void execute() {  light.on();  }  
}

使用命令对象

public class SimpleRemoteControl {  Command slot;  public SimpleRemoteControl() { }  public void setCommand(Command command) {  slot = command;  }  public void buttonWasPressed() {  slot.execute();  }  
}

NoCommand与空对象

NoCommand对象是一个空对象(null object)的例子。

  • 当你不想返回一个有意义的对象时,空对象就很有用
  • 客户可以将处理null的责任转移给空对象
  • 本例中:遥控器不可能出场时就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用execute方法时,这种对象什么事情都不做

许多设计模式中都会看到空对象,甚至有时空对象也被视为一种设计模式

定义命令模式

#HeadFirst设计模式7-命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

一个命令对象通过在特定接收者上绑定一组动作来封装一个请求

  • 命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用时,接收者会进行这些动作,
  • 从外面看,其他对象不知道接收者做了哪些具体动作,只知道调用execute()就能达成目标
    命令模式类图

Q:接收者一定有必要存在吗?为何命令对象不直接实现execute()方法的细节?
A:一般来说,我们尽量设计“傻瓜”命令对象。它只懂得调用一个接收者的一个行为。然而,有许多“聪明”命令对象会实现许多逻辑,直接完成一个请求。但是“聪明”命令对象的调用者和接收者之间的解耦程度不如“傻瓜”命令对象,而且你不能把接收者当成参数传给命名

支持撤销功能

  1. 当命令支持撤销时,该命令就必须提供和execute()方法相反的undo()方法。
    • 不管execute()刚才做什么,undo()都会倒转过来
public interface Command {public void execute();public void undo();
}
  1. LightOnCommand为例,如果LightOnCommandexecute()方法被调用,那么最后被调用的是on()方法,所以undo()需要执行的即为相反的off()
public class LightOnCommand implements Command {  private Light light;  public LightOnCommand(Light light) {  this.light = light;  }  public void execute() {  light.on();  }  public void undo() {  light.off();  }  
}
  1. 遥控器类中加入一个新的实例变量用来追踪最后被调用的命令。这样当撤销按钮被按下后,我们都可以取出这个命令并调用它的undo()方法
package remotecontrol;  import command.Command;  
import command.NoCommand;  public class RemoteControlWithUndo {  Command[] onCommands;  Command[] offCommands;  Command undoCommand;  public RemoteControlWithUndo() {  onCommands = new Command[7];  offCommands = new Command[7];  Command noCommand = new NoCommand();  for (int i = 0; i < 7; i++) {  onCommands[i] = noCommand;  offCommands[i] = noCommand;  }  undoCommand = noCommand;  }  public void setCommand(int slot, Command onCommand, Command offCommand) {  onCommands[slot] = onCommand;  offCommands[slot] = offCommand;  }  public void onButtonWasPushed(int slot) {  onCommands[slot].execute();  undoCommand = onCommands[slot];  }  public void offButtonWasPushed(int slot) {  offCommands[slot].execute();  undoCommand = offCommands[slot];  }  public void undoButtonWasPushed() {  undoCommand.undo();  }  public String toString() {  StringBuilder stringBuilder = new StringBuilder();  stringBuilder.append("\n------ Remote Control ------\n");  for (int i = 0; i < onCommands.length; i++) {  stringBuilder.append("[slot " + i + "]" + onCommands[i].getClass().getName()  + "    " + offCommands[i].getClass().getName() + "\n");  }  return stringBuilder.toString();  }  
}

使用状态实现撤销

例如,风扇具有多种转速,当撤销时,我们就需要考虑如何恢复它的上一个转速。

方法:加入prevSpeed变量对历史状态进行记录

多层次撤销

使用一个堆栈记录操作过程的每一个命令
当撤销时,从堆栈中取出最上层的命令,然后调用其undo()方法

One One One …… more things

宏命令

按下一个按钮,同时完成多个任务

制造一个新的命令,用来执行其他一堆命令

public class MacroCommand implements Command{Command[] commands;  public MacroCommand(Command[] commands) {  this.commands = commands;  }  public void execute() {  for (int i = 0; i < commands.length; i++) {  commands[i].execute();;  }  }  
}

Q:为什么不创建一个PartyCommand(),在其中调用其他的命令
A:这相当于把Party模式“硬编码”到PartyCommand中。而利用宏命令,你可以动态地决定PartyCommand是由哪些命令组成,所以宏命令在使用上更灵活。一般来说,宏命令的做法更优雅,也需要较少的新代码。

使用宏命令
  1. 创建想要进入宏的命令集合
  2. 创建两个数组,其中一个用来记录开启命令,另一个用来记录关闭命令,并在数组内放入对应命令
  3. 将宏命令指定给我们希望的按钮

队列请求

命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。
现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,他甚至可以在不同的线程中被调用。
利用这样的特性可以衍生出一些应用:日程安排(Scheduler)、线程池、工作队列等

想象有一个工作队列:一端添加命令,另一端则是线程

  • 线程进行下面的动作,从队列中取出一个命令,调用它的execute()方法,调用完成后将命令对象丢弃,再取出下一个命令
    命令队列

注意:工作队列类和进行计算的对象之间完全是解耦的。此刻线程可能在进行财务运算,下一刻却在读取网络数据。工作队列对象不在乎到底做些生命,它们只知道取出命令对象,然后调用execute()方法。
类似的,只要是实现命令模式的对象,就可以放入队列里,当线程可用时就调用此对象的execute()方法。

日志请求

某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的装填。

命令模式能够支持这一点:新增两个方法store()load()

  • java中,利用对象的序列化(Serialization)实现这些方法,但是一般认为序列化最好还是只用在对象的持久化上(persistence)

执行命令时,将历史记录存储在磁盘中,一旦系统司机,就可以将命令对象重新加载,并成批次地依次调用这些对象的execute()方法

许多大型数据结构的动作的应用无法在每次改变发生时被快速地存储。通过使用记录日志,我们可以将上次检查点(checkpoint)之后的所有操作记录下来,如果系统出状况,从检查点开始应用这些操作

对更高级的应用而言,这些技巧可以被扩展应用到事务(transaction)处理中。

总结

OO基础

  • 抽象
  • 封装
  • 多态
  • 继承

OO原则

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程
  • 为交互对象之间的松耦合设计而努力
  • 对扩展开放,对修改关闭
  • 依赖抽象,不要依赖具体类

OO模式

  • 命令模式——将请求封装成对象,这可以让你使用不同的请求、队列或者日志请求来参数化其他对象。命令模式也可以支持撤销操作。

要点

  • 命令模式将发出请求的对象和执行请求的对象解耦
  • 被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作
  • 调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用
  • 调用者可以接受命令当做参数,甚至在运行时动态地进行
  • 命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态
  • 宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也可以支持撤销
  • 实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接收者
  • 命令也可以用来实现日志和事务系统

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/22346.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于YOLO11深度学习的运动鞋品牌检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

DAY08 List接口、Collections接口、Set接口

学习目标 能够说出List集合特点1.有序2.允许存储重复的元素3.有带索引的方法(练习 add,remove,set,get) 能够使用集合工具类Collections类:static void sort(List<T> list) 根据元素的自然顺序 对指定列表按升序进行排序。static <T> void sort(List<T> lis…

shell编程总结

前言 shell编程学习总结&#xff0c;1万3千多字带你学习shell编程 往期推荐 14wpoc&#xff0c;nuclei全家桶&#xff1a;nuclei模版管理工具Nuclei 哥斯拉二开&#xff0c;免杀绕过规避流量检测设备 fscan全家桶&#xff1a;FscanPlus&#xff0c;fs&#xff0c;fscan适用…

OpenAI ChatGPT在心理治疗领域展现超凡同理心,通过图灵测试挑战人类专家

近期&#xff0c;一项关于OpenAI ChatGPT在心理治疗领域的研究更是引起了广泛关注。据报道&#xff0c;ChatGPT已经成功通过了治疗师领域的图灵测试&#xff0c;其表现甚至在某些方面超越了人类治疗师&#xff0c;尤其是在展现同理心方面&#xff0c;这一发现无疑为AI在心理健康…

【智能客服】ChatGPT大模型话术优化落地方案

本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 一、项目背景 1.1 行业背景 1.2 业务现…

【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?

文章目录 &#x1f30d;一. 数据交换--JSON❄️1. JSON介绍❄️2. JSON 快速入门❄️3. JSON 对象和字符串对象转换❄️4. JSON 在 java 中使用❄️5. 代码演示 &#x1f30d;二. 异步请求--Ajax❄️1. 基本介绍❄️2. JavaScript 原生 Ajax 请求❄️3. JQuery 的 Ajax 请求 &a…

[Android]APP自启动

APP添加自启动权限&#xff0c;重启设备后自动打开APP。 1.AndroidManifest.xml <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.an…

Moonshot AI 新突破:MoBA 为大语言模型长文本处理提效论文速读

前言 在自然语言处理领域&#xff0c;随着大语言模型&#xff08;LLMs&#xff09;不断拓展其阅读、理解和生成文本的能力&#xff0c;如何高效处理长文本成为一项关键挑战。近日&#xff0c;Moonshot AI Research 联合清华大学、浙江大学的研究人员提出了一种创新方法 —— 混…

cs224w课程学习笔记-第2课

cs224w课程学习笔记-第2课 传统图学习 前言一、节点任务1、任务背景2、特征节点度3、特征节点中心性3.1 特征向量中心性&#xff08;Eigenvector Centrality&#xff09;3.2 中介中心性&#xff08;Betweenness Centrality&#xff09;3.3 接近中心性&#xff08;Closeness Cen…

Centos虚拟机扩展磁盘空间

Centos虚拟机扩展磁盘空间 扩展前后效果1 虚拟机vmware关机后&#xff0c;编辑2 扩展2.1 查看2.2 新建分区2.3 格式化新建分区ext42.3.1 格式化2.3.2 创建2.3.3 修改2.3.4 查看 2.4 扩容2.4.1 扩容2.4.1 查看 扩展前后效果 df -h1 虚拟机vmware关机后&#xff0c;编辑 2 扩展 …

1.13作业

1 if(!preg_match("/[0-9]|\~|\|\|\#|\\$|\%|\^|\&|\*|\&#xff08;|\&#xff09;|\-|\|\|\{|\[|\]|\}|\:|\|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){eval($c); 构造数组rce ?ceval(array_pop(next(get_defined_vars()))); post传参:asystem("c…

如何在 SpringBoot 项目使用 Redis 的 Pipeline 功能

本文是博主在批量存储聊天中用户状态和登陆信息到 Redis 缓存中时&#xff0c;使用到了 Pipeline 功能&#xff0c;并对此做出了整理。 一、Redis Pipeline 是什么 Redis 的 Pipeline 功能可以显著提升 Redis 操作的性能&#xff0c;性能提升的原因在于可以批量执行命令。当我…

力扣LeetCode: 2209 用地毯覆盖后的最少白色砖块

题目&#xff1a; 给你一个下标从 0 开始的 二进制 字符串 floor &#xff0c;它表示地板上砖块的颜色。 floor[i] 0 表示地板上第 i 块砖块的颜色是 黑色 。floor[i] 1 表示地板上第 i 块砖块的颜色是 白色 。 同时给你 numCarpets 和 carpetLen 。你有 numCarpets 条 黑…

RabbitMQ 消息队列

1. 消息队列是什么&#xff1f; 当用户注册成功后&#xff0c;就发送邮件。当邮件发送成功了&#xff0c;接口才会提示注册成功信息。但由于发送邮件&#xff0c;依赖于其他厂商的服务&#xff0c;有可能他们的接口会非常耗时。那么用户就一直要等着邮件发送成功了&#xff0c;…

【SQL实验】触发器

下载素材文件”tsgl”、“成绩管理”,将tsgl.bak和成绩管理.bak数据库还原到库中【导入操作在之前的文章中详细讲过】 触发器 1、为图书表设置更新触发器&#xff0c;根据总编号来更新书名、作者、出版社、分类号和单价(根据总编号找到相应记录&#xff0c;然后更新书名、作者…

Win10系统Docker+DeepSeek+ragflow搭建本地知识库

文章目录 1、安装ollama1.1 下载1.2 安装1.3 cmd命令行测试安装成功1.4 拉取模型2、安装ragflow2.1 下载项目2.2 通过docker拉取镜像安装2.3 查看docker日志是否安装成功3、模型配置3.1 第一次登录需要注册3.2 模型添加4、知识库配置4.1 创建知识库4.2 上传文档4.3 解析5、聊天…

redis的应用,缓存,分布式锁

1.应用 1.1可以用作缓存 作用&#xff1a;提交数据的查询效率&#xff0c;减少对数据库的访问频率 什么数据适合放入缓存 1.查询频率高&#xff0c;修改频率低 2.对安全系数比较低 如何实现 Service public class DeptServer {Autowiredprivate DeptMapper deptMapper;Auto…

springboot整合 xxl-job

文章目录 一、xxl-job是什么二、使用步骤 1. 下载并运行管理端代码2. 访问管理页面&#xff0c;确认是否启动成功3. 配置执行器【在自己的springboot项目中配置】4. 在页面上创建执行器和任务&#xff0c;与项目中绑定 总结参考 一、xxl-job是什么 XXL-JOB 是一个分布式任务调…

Jenkins 环境搭建---基于 Docker

前期准备 提前安装jdk、maven、nodeJs&#xff08;如果需要的话&#xff09; 创建 jenkins 环境目录&#xff0c;用来当做挂载卷 /data/jenkins/ 一&#xff1a;拉取 Jenkins 镜像 docker pull jenkins/jenkins:lts 二&#xff1a;设置 Jenkins挂载目录 mkdir -p ~/jen…

小米路由器 AX3000T 降级后无法正常使用,解决办法

问题描述 买了个 AX3000T 路由器&#xff0c;想安装 OpenWRT 或者 安装 Clash 使用&#xff0c;看教程说是需要降级到 v1.0.47 版本。 结果刷机之后路由器无法打开了&#xff0c;一直黄灯亮&#xff0c;中间灭一下&#xff0c;又是黄灯长亮&#xff0c;没有 WIFI 没有连接。以…