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


    意图

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

    命令模式的诞生

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

    开发】:按键?不存在的,对我来说就是请求罢了,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 = "
    ";
    }
    
    

    封装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》两本经典书籍中的案例
    • 提供了友好的阅读指导

  • 相关阅读:
    背完这444句,你的口语绝对不成问题了
    过滤HTML
    Asp.net页面的生命周期
    查询分组中的前几条记录
    offsetLeft,Left,clientLeft的区别
    可以用javascript实现的10种图片特效
    了解黑客经常使用哪些工具
    js日历控件
    asp.net中的path备忘录
    ASP.NET MVC3 向View传递数据
  • 原文地址:https://www.cnblogs.com/kkzhilu/p/13264165.html
Copyright © 2020-2023  润新知