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

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

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

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

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

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

父级接口

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)去管理,达到解耦的目的

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协议,我在之后的文章会专门出一篇讲解~

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;
    }
}

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";
}

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

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模式:

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

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

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

GitHub地址

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

https://juejin.im/post/5ed5265851882543477c7abf

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论