[Серия «Учимся вместе»] Командный режим: инкапсулировать простого джедая?

Шаблоны проектирования
[Серия «Учимся вместе»] Командный режим: инкапсулировать простого джедая?

намерение

Инкапсулирует запрос в виде объекта, позволяя вам параметризовать клиентов с различными запросами, ставить в очередь или регистрировать запросы и поддерживать операции, которые нельзя отменить.

Рождение шаблона команд

[Продукт]: Брат-разработчик, приходите на работу, нам нужно спроектировать пульт дистанционного управления, основная функция - несколько кнопок, но может потребоваться управление многими различными марками оборудования, вы можете подумать об этом ~

【Развитие】: Кнопка? Его не существует, это просто просьба ко мне, Босс, помогите подумать, как адаптироваться к разным маркам техники?

[БОСС]: Мы не можем просто полагаться на нас в адаптации устройства. Это результат сотрудничества. Поскольку вы также сказали, что кнопка — это просто запрос, вы можете рассмотреть возможность использования командного режима для инкапсуляции запроса в виде объекта. Мы берем на себя инициативу связать исполнителей, соответствующих разным брендам, понятно?

【Развитие】: А? О, понял, понял (я понял!)

Основной код 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();
}

Идея оформления командного режима:

  • Команда объявляет интерфейс для команды
  • ConcreteCommand конкретное действие | команда
  • Клиент клиентский запрос
  • Команда и получатель привязки Invoker
  • Receiver Receiver умеет реализовывать операции, связанные с выполнением запроса, любой класс может быть приемником

Суть кода: абстрагировать запрос в команду, отделить получателя выполнения команды от самой команды и передать его стороннему классу (Invoker) для управления им для достижения цели разделения.

Попробуйте инкапсулировать простых джедаев в командном режиме

Советы по протоколу Redis

Redis — это REmote Dictionary Server (служба удаленного словаря);

Спецификация протокола Redis — это протокол сериализации Redis (протокол сериализации Redis).

RESP — это протокол связи, который ранее использовался клиентом и сервером Redis;

Особенности RESP: простая реализация, быстрый синтаксический анализ, хорошая читабельность.

Соглашение заключается в следующем:

Клиент отправляет команды на сервер в указанном формате

/***
 * set key value 协议翻译如下:
 * ​
 * * 3    ->  表示以下有几组命令
 * ​
 * $ 3    ->  表示命令长度是3
 * SET
 * ​
 * $6     ->  表示长度是6
 * keykey
 * ​
 * $5     ->  表示长度是5
 * value
 * ​
 * 完整即:
 * * 3
 * $ 3
 * SET
 * $6
 * keykey
 * $5 
 * value
 */

Что касается протокола RESP, связанного с Redis, я дам специальное объяснение в следующих статьях~

Инкапсулирует команду 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";
}

Инвокер пакета

Используя стек для хранения команд можно хорошо контролировать изменения команд и т.д.

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
 */
// 测试成功~

Объем кода немного мал. Если вам нужно увидеть подробности, перейдите по соответствующей ссылке кода внизу ~

какие сценарии применяются

Режим командного метода можно использовать в следующих ситуациях:

  • Необходимо абстрагировать действие, которое необходимо выполнить, чтобы параметризовать объект
  • Задавайте, ставьте в очередь и выполняйте запросы в разные моменты времени
  • Поддержка отмены операции

Код / практическое применение в жизни

В повседневной жизни существует понятие порядка, почему мы делаем заказ, а официант или другой персонал полностью понимает наше намерение? Именно потому, что мы построили команду по правилам, которые они составили, поэтому нет необходимости в послойной коммуникации в процессе взаимодействия, что удобно для развязки.

UML-диаграмма

Принципы дизайна, которым нужно следовать

  • Программирование для интерфейса, а не для реализации
  • Стремитесь к слабосвязанному дизайну взаимодействующих объектов
  • Классы должны быть открыты для расширения и закрыты для модификации

Связанные ссылки на код

Адрес GitHub

  • С учетом кейсов в двух классических книгах "HeadFirst" и "GOF"
  • Предоставляет дружественное руководство по чтению