意图

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

命令模式的诞生

【产品】:开发小哥,来活啦,咱们需要设计一款遥控器,核心功能就是几个按键,但是可能要控制很多不同品牌的设备,你们构思构思吧~

【开发】:按键?不存在的,对我来说就是请求罢了,Boss,帮我想一下怎么适配不同的品牌的设备啊?

【BOSS】:适配设备这个事,仅仅靠我们是不行的,这都是配合的结果,你既然也说了什么按钮只不过是请求而已,那可以考虑使用命令模式,把请求封装为对象,由我们主动去绑定不同品牌对应的执行者,懂了吗?

【开发】:哈?哦,懂了懂了(我懂个鬼!)

HeadFirst 核心代码

父级接口

public interface Command {
void execute();
}

封装请求为一个对象

public class LightOnCommand implements Command {

    Light light;

    public LightOnCommand(Light light) {
this.light = light;
} @Override
public void execute() {
light.on();
}
}

请求响应的Api

public class Light {

    /***
* on方法
*/
public void on() {
System.out.println("On...");
} /***
* off方法
*/
public void off() {
System.out.println("Off...");
}
}

调用方代码

public class SimpleRemoteControl {

    Command slot;

    public SimpleRemoteControl() {}

    public void setCommand(Command command) {
slot = command;
} public void buttonWasPressed() {
slot.execute();
}
} //****************************************** public static void main(String[] args) {
SimpleRemoteControl remote = new SimpleRemoteControl(); Light light = new Light(); LightOnCommand lightOn = new LightOnCommand(light);
remote.setCommand(lightOn);
remote.buttonWasPressed(); LightOffCommand lightOff = new LightOffCommand(light);
remote.setCommand(lightOff);
remote.buttonWasPressed();
}

命令模式的设计思路:

  • Command 声明命令的接口
  • ConcreteCommand 具体的动作 | 命令
  • Client 客户端请求
  • Invoker 绑定命令与接收者
  • Receiver 接收者 知道如何实施与执行一个请求相关的操作,任何类都可以是接收者

代码的核心即:把请求抽象为一个命令,把执行命令的接收者和命令本身分离,交由第三方类(Invoker)去管理,达到解耦的目的

试试用命令模式封装简单Jedis

Redis协议Tips

Redis 即 REmote Dictionary Server (远程字典服务);

而Redis的协议规范是 Redis Serialization Protocol (Redis序列化协议)

RESP 是redis客户端和服务端之前使用的一种通讯协议;

RESP 的特点:实现简单、快速解析、可读性好

协议如下:

客户端以规定格式的形式发送命令给服务器

set key value 协议翻译如下:

* 3    ->  表示以下有几组命令

$ 3    ->  表示命令长度是3
SET $6 -> 表示长度是6
keykey $5 -> 表示长度是5
value 完整即:
* 3
$ 3
SET
$6
keykey
$5
value

关于Redis相关的RESP协议,我在之后的文章会专门出一篇讲解~

封装Get命令

public class GetCommand implements Command {

    private GetReceiver receiver;

    private String arg;

    @Override
public void execute() {
receiver.doCommand(this.arg);
} public GetCommand(GetReceiver receiver, String arg) {
this.receiver = receiver;
this.arg = arg;
}
}

封装Get接收者

public class GetReceiver {

    OutputStream write;

    InputStream read;

    public void doCommand (String arg) {
String[] strings = arg.split(" ");
String key = strings[0];
byte[] bytes;
try {
String sb = "*2" + SPILT +
"$3" + SPILT +
"GET" + SPILT +
"$" + key.getBytes().length + SPILT +
key + SPILT;
write.write(sb.getBytes());
bytes = new byte[1024];
read.read(bytes);
System.out.println("Result: " + new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
} public GetReceiver(OutputStream write, InputStream read) {
this.write = write;
this.read = read;
} final String SPILT = "\r\n";
}

封装Invoker

利用栈存储命令,可以很好的控制命令的变化等等

public class Invoker {

    private final Stack<Command> commands;

    public Invoker() {
commands = new Stack<>();
} public void addCommand(Command command) {
commands.push(command);
} public void undoCommand() {
if (!commands.empty()) {
commands.pop();
}
} public void execute() {
while (!commands.empty()) {
Command command = commands.pop();
command.execute();
}
}
}

测试类

	/***
* 简易Jedis代码, 利用栈存储命令(可根据需求更改数据结构)
*
* 推荐阅读顺序:
* @see Command
* @see GetCommand | SetCommand
* @see GetReceiver | SetReceiver
* @see Invoker
*/
public static void main(String[] args) throws IOException {
// 初始化Socket流
Socket socket = new Socket("127.0.0.1", 6379);
OutputStream write = socket.getOutputStream();
InputStream read = socket.getInputStream(); Invoker invoker = new Invoker(); // 初始化Get | Set任务执行者
GetReceiver getReceiver = new GetReceiver(write, read);
SetReceiver setReceiver = new SetReceiver(write, read); // 测试get命令
invoker.addCommand(new GetCommand(getReceiver, "key")); // 测试set命令
invoker.addCommand(new SetCommand(setReceiver, "key xixixi")); // 测试get命令
invoker.addCommand(new GetCommand(getReceiver, "key")); // 测试get命令
invoker.addCommand(new GetCommand(getReceiver, "key")); // 测试撤销上一个命令 -> 输出四次则测试失败,三次则成功
invoker.undoCommand();
invoker.execute();
}

输出结果:

Result: $4
test Result: +OK Result: $6
xixixi // 测试成功~

代码量有点小多,需要看详情的话,请跳转到最下面的相关代码链接吧~

什么场景适用

在下列情况下可以使用 Command Method模式:

  • 需要抽象出待执行的动作以参数化某对象
  • 在不同的时刻指定,排列和执行请求
  • 支持取消操作

Code/生活中的实际应用

在日常生活中都有订单的概念,为什么我们下订单,服务员或者其他工作人员完全明白我们的意图呢?就是因为我们按照他们制定的规则构建起了一个命令,那么在交互过程就不需要层层沟通,方便解耦。

UML图

遵循的设计原则

  • 针对接口编程,不针对实现编程
  • 为交互对象松耦合设计而努力
  • 类应该对拓展开放,对修改关闭

相关代码链接

GitHub地址

  • 兼顾了《HeadFirst》以及《GOF》两本经典书籍中的案例
  • 提供了友好的阅读指导

【一起学系列】之命令模式:封装一个简单Jedis的更多相关文章

  1. Directx11学习笔记【四】 封装一个简单的Dx11DemoBase

    根据前面两个笔记的内容,我们来封装一个简单的基类,方便以后的使用. 代码和前面类似,没有什么新的内容,直接看代码吧(由于代码上次都注释了,这次代码就没怎么写注释o(╯□╰)o) Dx11DemoBas ...

  2. 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件

    先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets) WebSocket 是一 ...

  3. 代码改变世界 | 如何封装一个简单的 Koa

    下面给大家带来:封装一个简单的 Koa Koa 是基于 Node.js 平台的下一代 web 开发框架 Koa 是一个新的 web 框架,可以快速而愉快地编写服务端应用程序,本文将跟大家一起学习:封装 ...

  4. python+selenium之自定义封装一个简单的Log类

    python+selenium之自定义封装一个简单的Log类 一. 问题分析: 我们需要封装一个简单的日志类,主要有以下内容: 1. 生成的日志文件格式是 年月日时分秒.log 2. 生成的xxx.l ...

  5. Python之自定义封装一个简单的Log类

    参考:http://www.jb51.net/article/42626.htm 参考:http://blog.csdn.net/u011541946/article/details/70198676 ...

  6. Python+Selenium中级篇之8-Python自定义封装一个简单的Log类《转载》

    Python+Selenium中级篇之8-Python自定义封装一个简单的Log类: https://blog.csdn.net/u011541946/article/details/70198676

  7. C#设计模式系列:命令模式(Command)

    1.命令模式简介 1.1>.定义 命令模式的目的是解除命令发出者和接收者之间的紧密耦合关系,使二者相对独立,有利于程序的并行开发和代码的维护.命令模式的核心思想是将请求封装为一个对象,将其作为命 ...

  8. 设计模式总结篇系列:命令模式(Command)

    在程序设计中,经常会遇到一个对象需要调用另外一个对象的某个方法以达到某种目的,在此场景中,存在两个角色:请求发出者和请求接收者.发出者发出请求,接收者接收请求并进行相应处理.有时候,当需要对请求发出者 ...

  9. Java设计模式系列之命令模式

    命令模式(Command)的定义 将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化:对请求排队或记录日志,以及支持可撤销的操作,将”发出请求的对象”和”接收与执行这些请求的对象”分隔开来. ...

随机推荐

  1. Web测试和app测试区别?

    EB测试和APP测试从流程上来说,没有区别.都需要经历测试计划方案,用例设计,测试执行,缺陷管理,测试报告等相关活动.从技术上来说,WEB测试和APP测试其测试类型也基本相似,都需要进行功能测试,性能 ...

  2. WIN10下如何解决PL2303驱动不可用的问题或者com口显示黄色感叹号usb-to-serial

    WIN10下如何解决PL2303驱动不可用的问题或者com口显示黄色感叹号usb-to-serial

  3. TXT文件的写入及读出

    一.文件的读出: file = open('url/data.txt','r',encoding='utf-8')#打开模式r w a,当文件在当前工作区域直接写文件名:如果不在当前工作区域要写绝对地 ...

  4. 电商安全无小事,如何有效抵御 CSRF 攻击?

    现在,我们绝大多数人都会在网上购物买东西.但是很多人都不清楚的是,很多电商网站会存在安全漏洞.乌云就通报过,国内很多家公司的网站都存在 CSRF 漏洞.如果某个网站存在这种安全漏洞的话,那么我们在购物 ...

  5. JDK开发环境的搭建和环境变量的配置

    首先博主先说一下JDK.JRE.JVM的区别和联系.我们正常运行程序的话只需安装JRE就行啦,如果要编译运行Java程序就得需要JKD下的bin目录下的编译工具. JDK -- java develo ...

  6. 腾讯IEG--2020春招实习

    笔试 正常批就五道编程题,可以跳出使用本地IDE,题目很好理解,基本都能写出来,但是要过全部用例不容易.具体题目和题解可以看看这位大佬的牛客帖子,我的就不献丑了,有两题都只过了40%,我当时是用C#做 ...

  7. VulnHub PowerGrid 1.0.1靶机渗透

    ​本文首发于微信公众号:VulnHub PowerGrid 1.0.1靶机渗透,未经授权,禁止转载. 难度评级:☆☆☆☆☆官网地址:https://download.vulnhub.com/power ...

  8. CentOS 7 Nacos 集群搭建

    环境 CentOS 7.4 MySQL 5.7 nacos-server-1.1.2 本次安装的软件全部在 /home/javateam 目录下. MySQL 安装 首先下载 rpm 安装包,地址:h ...

  9. WAF产品记录

    WAF产品化 2011-1-13 目标:稳定的版本 和 标准的手册   1.硬件差异问题,争取了OEM提供硬件样机. 2.OEM功能本来在我们手里,为了更好产品化,配合移交工作. 3.我们做好 产品生 ...

  10. 弹性盒模型中flex-grow 和flex的区别

    在flex弹性盒模型体系中,flex-grow和flex都有对子元素进行放大的作用,但是这两个属性在放大时的计算方法不同,在使用时候要注意,使用正确的放大属性,从而达到自己想要的效果. 先来看下两个属 ...