JavaRush /Java Blog /Random-TW /讓我們實作命令模式來與機器人一起工作。(第 1 部分)-“Java 專案從頭到尾”
Roman Beekeeper
等級 35

讓我們實作命令模式來與機器人一起工作。(第 1 部分)-“Java 專案從頭到尾”

在 Random-TW 群組發布
大家好,親愛的朋友們。今天我們將根據我們的需求實現一個命令設計的模板(模板是一種模式,在我們的上下文中它是同一件事)。使用此模板,我們將方便、正確地處理機器人的命令。 「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