【一起学系列】之命令模式:封装一个简单Jedis

朴灿烈づ我的快乐病毒、 2023-02-14 10:11 82阅读 0赞

意图

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

命令模式的诞生

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

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

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

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

format_png

HeadFirst 核心代码

父级接口

  1. public interface Command {
  2. void execute();
  3. }

封装请求为一个对象

  1. public class LightOnCommand implements Command {
  2. Light light;
  3. public LightOnCommand(Light light) {
  4. this.light = light;
  5. }
  6. @Override
  7. public void execute() {
  8. light.on();
  9. }
  10. }

请求响应的Api

  1. public class Light {
  2. /*** * on方法 */
  3. public void on() {
  4. System.out.println("On...");
  5. }
  6. /*** * off方法 */
  7. public void off() {
  8. System.out.println("Off...");
  9. }
  10. }

调用方代码

  1. public class SimpleRemoteControl {
  2. Command slot;
  3. public SimpleRemoteControl() { }
  4. public void setCommand(Command command) {
  5. slot = command;
  6. }
  7. public void buttonWasPressed() {
  8. slot.execute();
  9. }
  10. }
  11. //******************************************
  12. public static void main(String[] args) {
  13. SimpleRemoteControl remote = new SimpleRemoteControl();
  14. Light light = new Light();
  15. LightOnCommand lightOn = new LightOnCommand(light);
  16. remote.setCommand(lightOn);
  17. remote.buttonWasPressed();
  18. LightOffCommand lightOff = new LightOffCommand(light);
  19. remote.setCommand(lightOff);
  20. remote.buttonWasPressed();
  21. }

命令模式的设计思路:

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

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

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

Redis协议Tips

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

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

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

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

协议如下:

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

  1. set key value 协议翻译如下:
  2. * 3 -> 表示以下有几组命令
  3. $ 3 -> 表示命令长度是3
  4. SET
  5. $6 -> 表示长度是6
  6. keykey
  7. $5 -> 表示长度是5
  8. value
  9. 完整即:
  10. * 3
  11. $ 3
  12. SET
  13. $6
  14. keykey
  15. $5
  16. value

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

封装Get命令

  1. public class GetCommand implements Command {
  2. private GetReceiver receiver;
  3. private String arg;
  4. @Override
  5. public void execute() {
  6. receiver.doCommand(this.arg);
  7. }
  8. public GetCommand(GetReceiver receiver, String arg) {
  9. this.receiver = receiver;
  10. this.arg = arg;
  11. }
  12. }

封装Get接收者

  1. public class GetReceiver {
  2. OutputStream write;
  3. InputStream read;
  4. public void doCommand (String arg) {
  5. String[] strings = arg.split(" ");
  6. String key = strings[0];
  7. byte[] bytes;
  8. try {
  9. String sb = "*2" + SPILT +
  10. "$3" + SPILT +
  11. "GET" + SPILT +
  12. "$" + key.getBytes().length + SPILT +
  13. key + SPILT;
  14. write.write(sb.getBytes());
  15. bytes = new byte[1024];
  16. read.read(bytes);
  17. System.out.println("Result: " + new String(bytes));
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. public GetReceiver(OutputStream write, InputStream read) {
  23. this.write = write;
  24. this.read = read;
  25. }
  26. final String SPILT = "\r\n";
  27. }

封装Invoker

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

  1. public class Invoker {
  2. private final Stack<Command> commands;
  3. public Invoker() {
  4. commands = new Stack<>();
  5. }
  6. public void addCommand(Command command) {
  7. commands.push(command);
  8. }
  9. public void undoCommand() {
  10. if (!commands.empty()) {
  11. commands.pop();
  12. }
  13. }
  14. public void execute() {
  15. while (!commands.empty()) {
  16. Command command = commands.pop();
  17. command.execute();
  18. }
  19. }
  20. }

测试类

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

输出结果:

  1. Result: $4
  2. test
  3. Result: +OK
  4. Result: $6
  5. xixixi
  6. // 测试成功~

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

什么场景适用

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

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

Code/生活中的实际应用

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

UML图

format_png 1

遵循的设计原则

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

相关代码链接

GitHub地址

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

format_png 2

发表评论

表情:
评论列表 (有 0 条评论,82人围观)

还没有评论,来说两句吧...

相关阅读