JavaRush /Java 博客 /Random-ZH /让我们实现命令模式来与机器人一起工作。(第 1 部分)-“Java 项目从头到尾”
Roman Beekeeper
第 35 级

让我们实现命令模式来与机器人一起工作。(第 1 部分)-“Java 项目从头到尾”

已在 Random-ZH 群组中发布
大家好,亲爱的朋友们。今天我们将根据我们的需求实现一个命令设计的模板(模板是一种模式,在我们的上下文中它是同一件事)。使用此模板,我们将方便、正确地处理机器人的命令。 “Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 1 - 1 部分
朋友们,你们喜欢Javarush Telegram Bot 项目吗?不要偷懒:给它一颗星。这样就能明显看出他是有趣的,培养他也会更愉快!
首先,我们最好谈谈这是什么样的模式——命令。但如果我这样做的话,文章就会非常庞大​​而且繁琐。因此,我选择了自学材料:
  1. 这是我4年前的文章。这是我大三的时候写的,所以不要太严厉地评价它。
  2. YouTube 上一位非常情绪化且互动的瑞典人的视频。我强烈推荐它。他说得很漂亮,他的英语清晰易懂。一般来说,他有一个关于其他设计模式的视频。
  3. 在我文章的评论中,有人Nullptr35推荐了这个视频
这应该足以让您沉浸在该主题中并与我达成共识。好吧,那些熟悉这种设计模式的人可以放心地跳过并继续。

我们写JRTB-3

一切都和以前一样:
  1. 我们更新主分支。
  2. 基于更新的主分支,我们创建一个新的JRTB-3
  3. 让我们实现该模式。
  4. 我们创建一个新的提交来描述已完成的工作。
  5. 我们创建一个拉取请求,检查它,如果一切正常,我们就合并我们的工作。
我不会展示第 1-2 点:我在之前的文章中非常详细地描述了它们,所以让我们直接实现模板。为什么这个模板适合我们?是的,因为我们每次执行命令时,都会进入onUpdateReceived(Update update)方法,并且根据命令的不同,我们会执行不同的逻辑。如果没有这种模式,我们就会有一大堆 if-else if 语句。像这样的东西:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
而且,有省略号的地方,可能还有几十个队伍。以及正常情况下该如何处理呢?如何支持?困难又困难。这意味着这个选项不适合我们。它应该看起来像这样:
if (message.startsWith(COMMAND_PREFIX)) {
   String commandIdentifier = message.split(" ")[0].toLowerCase();
   commandContainer.getCommand(commandIdentifier, userName).execute(update);
} else {
   commandContainer.getCommand(NO.getCommand(), userName).execute(update);
}
就这样!而且无论我们添加多少命令,这段代码都将保持不变。他在做什么?第一个 if 确保消息以命令前缀“/”开头。如果是这种情况,那么我们选择第一个空格之前的行,并在 CommandContainer 中查找相应的命令;一旦找到,我们就运行该命令。就这样……)如果您有愿望和时间,您可以实现与团队合作,首先在一个班级中一次,有一堆条件等等,然后使用模板。您会看到差异。那将是多么美丽啊!首先,让我们在 bot 包旁边创建一个包,将其称为command“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 1 - 2 部分这个包中已经包含了与命令的实现相关的所有类。我们需要一个接口来处理命令。对于这种情况,让我们创建它:
package com.github.javarushcommunity.jrtb.command;

import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Command interface for handling telegram-bot commands.
*/
public interface Command {

   /**
    * Main method, which is executing command logic.
    *
    * @param update provided {@link Update} object with all the needed data for command.
    */
   void execute(Update update);
}
此时,我们不需要实现命令的反向操作,因此我们将跳过该方法(取消执行)。在执行方法中, Update对象作为参数出现- 正是我们在机器人中的 main 方法中出现的参数。该对象将包含处理命令所需的所有内容。接下来,我们将添加一个枚举来存储命令值(开始、停止等)。我们为什么需要这个?这样我们就只有一个真实的球队名称来源。我们还在命令包中创建它。我们称之为CommandName
package com.github.javarushcommunity.jrtb.command;

/**
* Enumeration for {@link Command}'s.
*/
public enum CommandName {

   START("/start"),
   STOP("/stop");

   private final String commandName;

   CommandName(String commandName) {
       this.commandName = commandName;
   }

   public String getCommandName() {
       return commandName;
   }

}
我们还需要一项通过机器人发送消息的服务。为此,我们将在命令包旁边创建一个服务包,向其中添加所有必要的服务。 这里值得关注的是我在这种情况下所说的“服务”一词的含义。如果我们考虑一个应用程序,它通常分为几个层:用于处理端点的层 - 控制器,业务逻辑层 - 服务,以及用于处理数据库的层 - 存储库。因此,在我们的例子中,服务是实现某种业务逻辑的类。 如何正确创建服务?首先,为其创建一个接口和一个实现。使用“@Service”注释将实现添加到 SpringBoot 应用程序的应用程序上下文中,并在必要时使用“@Autowired”注释将其收紧。因此,我们创建 SendBotMessageService 接口(在命名服务中,他们通常在名称末尾添加 Service):
package com.github.javarushcommunity.jrtb.service;

/**
* Service for sending messages via telegram-bot.
*/
public interface SendBotMessageService {

   /**
    * Send message via telegram bot.
    *
    * @param chatId provided chatId in which messages would be sent.
    * @param message provided message to be sent.
    */
   void sendMessage(String chatId, String message);
}
接下来,我们创建它的实现:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

/**
* Implementation of {@link SendBotMessageService} interface.
*/
@Service
public class SendBotMessageServiceImpl implements SendBotMessageService {

   private final JavarushTelegramBot javarushBot;

   @Autowired
   public SendBotMessageServiceImpl(JavarushTelegramBot javarushBot) {
       this.javarushBot = javarushBot;
   }

   @Override
   public void sendMessage(String chatId, String message) {
       SendMessage sendMessage = new SendMessage();
       sendMessage.setChatId(chatId);
       sendMessage.enableHtml(true);
       sendMessage.setText(message);

       try {
           javarushBot.execute(sendMessage);
       } catch (TelegramApiException e) {
           //todo add logging to the project.
           e.printStackTrace();
       }
   }
}
这就是实现的样子。最重要的魔力在于设计师的诞生地。在构造函数上使用@Autowired注释,SpringBoot将在其应用程序上下文中查找此类的对象。他已经在那里了。它的工作原理如下:在我们的应用程序中,我们可以在任何地方访问机器人并执行某些操作。而这个服务负责发送消息。这样我们就不会每次在每个地方都写这样的东西:
SendMessage sendMessage = new SendMessage();
sendMessage.setChatId(chatId);
sendMessage.setText(message);

try {
   javarushBot.execute(sendMessage);
} catch (TelegramApiException e) {
   //todo add logging to the project.
   e.printStackTrace();
}
我们已将此逻辑移至一个单独的类中,并在必要时使用它。现在我们需要实现三个命令:StartCommand、StopCommand 和 UnknownCommand。我们需要它们,以便我们有东西来填充我们的命令容器。目前,文本将是枯燥且缺乏信息的;对于本任务的目的来说,这并不是很重要。所以,启动命令:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Start {@link Command}.
*/
public class StartCommand implements Command {

   private final SendBotMessageService sendBotMessageService;

   public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot. Я помогу тебе быть в курсе последних " +
           "статей тех авторов, котрые тебе интересны. Я еще маленький и только учусь.";

   // Здесь не добавляем сервис через получение из Application Context.
   // Потому что если это сделать так, то будет циклическая зависимость, которая
   // ломает работу applications.
   public StartCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), START_MESSAGE);
   }
}
请设计师仔细阅读评论。由于架构不太正确,可能会出现循环依赖(circular dependency )。就我们而言,我们将确保一切正常且正确。当创建 CommandContainer 中已有的命令时,将添加应用程序上下文中的真实对象。停止命令:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Stop {@link Command}.
*/
public class StopCommand implements Command {

   private final SendBotMessageService sendBotMessageService;

   public static final String STOP_MESSAGE = "Деактивировал все ваши подписки \uD83D\uDE1F.";

   public StopCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
   }
}
和未知命令。为什么我们需要它?对于我们来说,这是一个重要的命令,如果我们找不到给我们的命令,它将做出响应。我们还需要 NoCommand 和 HelpCommand。
  • NoCommand - 负责处理消息根本不以命令开头的情况;
  • HelpCommand 将成为用户的指南,一种文档。
让我们添加帮助命令:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

import static com.github.javarushcommunity.jrtb.command.CommandName.*;

/**
* Help {@link Command}.
*/
public class HelpCommand implements Command {

   private final SendBotMessageService sendBotMessageService;

   public static final String HELP_MESSAGE = String.format("✨<b>Дотупные команды</b>✨\n\n"

                   + "<b>Начать\\закончить работу с ботом</b>\n"
                   + "%s - начать работу со мной\n"
                   + "%s - приостановить работу со мной\n\n"
                   + "%s - получить помощь в работе со мной\n",
           START.getCommandName(), STOP.getCommandName(), HELP.getCommandName());

   public HelpCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), HELP_MESSAGE);
   }
}
无命令:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* No {@link Command}.
*/
public class NoCommand implements Command {

   private final SendBotMessageService sendBotMessageService;

   public static final String NO_MESSAGE = "Я поддерживаю команды, начинающиеся со слеша(/).\n"
           + "Whatбы посмотреть список команд введите /help";

   public NoCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), NO_MESSAGE);
   }
}
对于这个任务,仍然有 UnknownCommand:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Unknown {@link Command}.
*/
public class UnknownCommand implements Command {

   public static final String UNKNOWN_MESSAGE = "Не понимаю вас \uD83D\uDE1F, напишите /help чтобы узнать что я понимаю.";

   private final SendBotMessageService sendBotMessageService;

   public UnknownCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), UNKNOWN_MESSAGE);
   }
}
接下来,让我们为命令添加一个容器。它将存储我们的命令对象,并根据请求我们期望收到所需的命令。我们称之为CommandContainer
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.google.common.collect.ImmutableMap;

import static com.github.javarushcommunity.jrtb.command.CommandName.*;

/**
* Container of the {@link Command}s, which are using for handling telegram commands.
*/
public class CommandContainer {

   private final ImmutableMap<String, Command> commandMap;
   private final Command unknownCommand;

   public CommandContainer(SendBotMessageService sendBotMessageService) {

       commandMap = ImmutableMap.<string, command="">builder()
               .put(START.getCommandName(), new StartCommand(sendBotMessageService))
               .put(STOP.getCommandName(), new StopCommand(sendBotMessageService))
               .put(HELP.getCommandName(), new HelpCommand(sendBotMessageService))
               .put(NO.getCommandName(), new NoCommand(sendBotMessageService))
               .build();

       unknownCommand = new UnknownCommand(sendBotMessageService);
   }

   public Command retrieveCommand(String commandIdentifier) {
       return commandMap.getOrDefault(commandIdentifier, unknownCommand);
   }

}
正如你所看到的,一切都很简单。我们有一个不可变的映射,其中包含命令值形式的键和 Command 类型的命令对象形式的值。在构造函数中,我们填写一次不可变映射,并在应用程序的整个操作过程中访问它。使用容器的主要且唯一的方法是retrieveCommand(String commandIdentifier) ​​。有一个命令叫UnknownCommand,它负责处理我们找不到相应命令的情况。现在我们准备将容器实现到我们的机器人类中 - 在 JavaRushTelegramBot 中:这就是我们的机器人类现在的样子:
package com.github.javarushcommunity.jrtb.bot;

import com.github.javarushcommunity.jrtb.command.CommandContainer;
import com.github.javarushcommunity.jrtb.service.SendBotMessageServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.objects.Update;

import static com.github.javarushcommunity.jrtb.command.CommandName.NO;

/**
* Telegram bot for Javarush Community from Javarush community.
*/
@Component
public class JavarushTelegramBot extends TelegramLongPollingBot {

   public static String COMMAND_PREFIX = "/";

   @Value("${bot.username}")
   private String username;

   @Value("${bot.token}")
   private String token;

   private final CommandContainer commandContainer;

   public JavarushTelegramBot() {
       this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this));
   }

   @Override
   public void onUpdateReceived(Update update) {
       if (update.hasMessage() && update.getMessage().hasText()) {
           String message = update.getMessage().getText().trim();
           if (message.startsWith(COMMAND_PREFIX)) {
               String commandIdentifier = message.split(" ")[0].toLowerCase();

               commandContainer.retrieveCommand(commandIdentifier).execute(update);
           } else {
               commandContainer.retrieveCommand(NO.getCommandName()).execute(update);
           }
       }
   }

   @Override
   public String getBotUsername() {
       return username;
   }

   @Override
   public String getBotToken() {
       return token;
   }
}
就这样,代码的修改就完成了。我怎样才能检查这个?您需要启动机器人并检查一切是否正常。为此,我更新 application.properties 中的令牌,设置正确的令牌,然后在 JavarushTelegramBotApplication 类中启动应用程序:“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 1 - 3 部分现在我们需要检查命令是否按预期工作。我一步步检查:
  • 停止命令;
  • 启动命令;
  • 帮助命令;
  • 无命令;
  • 未知的命令。
发生的事情是这样的:“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 1 - 4 部分机器人的工作完全符合我们的预期。通过链接继续。

该系列所有材料的列表位于本文开头。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION