JavaRush /Java Blog /Random-TL /Ipatupad natin ang Command Pattern upang gumana sa bot. (...

Ipatupad natin ang Command Pattern upang gumana sa bot. (Bahagi 1) - "Proyekto ng Java mula A hanggang Z"

Nai-publish sa grupo
Kumusta sa lahat, mahal na mga kaibigan. Ngayon ay magpapatupad kami ng isang template (ang template ay isang pattern, sa aming konteksto ito ay ang parehong bagay) ng Command na disenyo para sa aming mga pangangailangan. Gamit ang template na ito, maginhawa at tama kaming gagana sa pagproseso ng mga command ng aming bot. "Proyekto ng Java mula A hanggang Z": Pagpapatupad ng Command Pattern para sa pagtatrabaho sa isang bot.  Bahagi 1 - 1
Mga kaibigan, gusto mo ba ang proyekto ng Javarush Telegram Bot ? Huwag maging tamad: bigyan ito ng bituin . Sa ganitong paraan magiging malinaw na siya ay kawili-wili, at magiging mas kaaya-aya na paunlarin siya!
Upang magsimula, makabubuting pag-usapan kung anong uri ito ng pattern - Command. Ngunit kung gagawin ko ito, ang artikulo ay magiging napakalaki at masalimuot. Samakatuwid, pumili ako ng mga materyales para sa pag-aaral sa sarili:
  1. Ito ang aking artikulo mula 4 na taon na ang nakakaraan. Isinulat ko ito noong ako ay isang junior, kaya't huwag masyadong husgahan ito.
  2. Video ng isang napaka-emosyonal at interactive na Swede sa YouTube. Inirerekomenda ko ito. Maganda siyang magsalita, malinaw at naiintindihan ang kanyang Ingles. At sa pangkalahatan, mayroon siyang video tungkol sa iba pang mga pattern ng disenyo.
  3. Sa mga komento sa aking artikulo, may isang Nullptr35 na nagrekomenda ng video na ito .
Ito ay dapat na sapat upang isawsaw ang iyong sarili sa paksa at maging sa parehong pahina tulad ng sa akin. Well, ang mga pamilyar sa pattern ng disenyo na ito ay maaaring ligtas na lumaktaw at magpatuloy.

Nagsusulat kami ng JRTB-3

Ang lahat ay katulad ng dati:
  1. Ina-update namin ang pangunahing sangay.
  2. Batay sa na-update na pangunahing sangay, lumikha kami ng bagong JRTB-3 .
  3. Ipatupad natin ang pattern.
  4. Lumilikha kami ng bagong pangako na naglalarawan sa gawaing ginawa.
  5. Gumagawa kami ng pull request, suriin ito, at kung ok ang lahat, pinagsama namin ang aming trabaho.
Hindi ko ipapakita ang mga puntos 1-2: Inilarawan ko ang mga ito nang maingat sa mga nakaraang artikulo, kaya diretso tayo sa pagpapatupad ng template. Bakit angkop ang template na ito para sa amin? Oo, dahil sa tuwing magpapatupad tayo ng utos, pupunta tayo sa onUpdateReceived(Update update) method , at depende sa utos gagawa tayo ng iba't ibang lohika. Kung wala ang pattern na ito, magkakaroon tayo ng isang buong host ng if-else if statements. Isang bagay na tulad nito:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
Bukod dito, kung saan mayroong isang ellipsis, maaaring mayroong ilang dosenang higit pang mga koponan. At paano pangasiwaan ito nang normal? Paano suportahan? Mahirap at mahirap. Nangangahulugan ito na ang pagpipiliang ito ay hindi angkop sa amin. Dapat itong magmukhang ganito:
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);
}
Iyon lang! At gaano man karaming mga utos ang idaragdag namin, ang seksyong ito ng code ay mananatiling hindi nagbabago. Ano ang ginagawa niya? Ang una kung tinitiyak na ang mensahe ay nagsisimula sa command prefix na "/". Kung ito ang kaso, pagkatapos ay piliin namin ang linya hanggang sa unang puwang at hanapin ang kaukulang command sa CommandContainer; sa sandaling mahanap namin ito, patakbuhin namin ang command. At iyon lang...) Kung mayroon kang pagnanais at oras, maaari mong ipatupad ang pakikipagtulungan sa mga koponan, una sa isang klase nang sabay-sabay, na may isang grupo ng mga kundisyon at lahat ng iyon, at pagkatapos ay gumagamit ng isang template. Makikita mo ang pagkakaiba. Ano ang magiging kagandahan nito! Una, gumawa tayo ng package sa tabi ng bot package, na tatawaging command . "Proyekto ng Java mula A hanggang Z": Pagpapatupad ng Command Pattern para sa pagtatrabaho sa isang bot.  Bahagi 1 - 2At nasa package na ito magkakaroon ng lahat ng mga klase na nauugnay sa pagpapatupad ng utos. Kailangan namin ng isang interface para sa pagtatrabaho sa mga command. Para sa kasong ito, gawin natin ito:
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);
}
Sa puntong ito, hindi namin kailangang ipatupad ang reverse operation ng command, kaya laktawan namin ang pamamaraang ito (unexecute). Sa execute na paraan, ang Update object ay dumating bilang isang argumento - eksakto ang isa na dumarating sa aming pangunahing paraan sa bot. Ang bagay na ito ay maglalaman ng lahat ng kailangan para maproseso ang utos. Susunod, magdaragdag kami ng isang enum na mag-iimbak ng mga halaga ng command (simula, huminto, at iba pa). Bakit kailangan natin ang mga ito? Upang magkaroon lamang tayo ng isang mapagkukunan ng katotohanan para sa mga pangalan ng koponan. Ginagawa rin namin ito sa aming command package . Tawagin natin itong 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;
   }

}
Kailangan din namin ng serbisyo na magpapadala ng mga mensahe sa pamamagitan ng bot. Upang gawin ito, gagawa kami ng isang service package sa tabi ng command package , kung saan idaragdag namin ang lahat ng kinakailangang serbisyo. Narito ito ay nagkakahalaga ng pagtuon sa kung ano ang ibig kong sabihin sa salitang serbisyo sa kasong ito. Kung isasaalang-alang namin ang isang application, madalas itong nahahati sa ilang mga layer: isang layer para sa pagtatrabaho sa mga endpoint - mga controller, isang layer ng business logic - mga serbisyo, at isang layer para sa pagtatrabaho sa database - isang repository. Samakatuwid, sa aming kaso, ang isang serbisyo ay isang klase na nagpapatupad ng ilang uri ng lohika ng negosyo. Paano gumawa ng isang serbisyo nang tama? Una, lumikha ng isang interface para dito at isang pagpapatupad. Idagdag ang pagpapatupad gamit ang `@Service` annotation sa Application Context ng aming SpringBoot application, at, kung kinakailangan, higpitan ito gamit ang `@Autowired` annotation. Samakatuwid, ginagawa namin ang SendBotMessageService na interface (sa pagbibigay ng pangalan sa mga serbisyo ay karaniwang idinadagdag nila ang Serbisyo sa dulo ng pangalan):
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);
}
Susunod, nilikha namin ang pagpapatupad nito:
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();
       }
   }
}
Ito ang hitsura ng pagpapatupad. Ang pinakamahalagang magic ay kung saan nilikha ang taga-disenyo. Gamit ang @Autowired annotation sa constructor, maghahanap ang SpringBoot ng object ng klase na ito sa Application Context nito. At nandoon na siya. Ito ay gumagana tulad nito: sa aming application, kahit saan maaari naming ma-access ang bot at gumawa ng isang bagay. At responsable ang serbisyong ito sa pagpapadala ng mga mensahe. Upang hindi tayo magsulat ng ganito sa bawat oras sa bawat lugar:
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();
}
Inilipat namin ang lohika na ito sa isang hiwalay na klase at gagamitin ito kung kinakailangan. Ngayon kailangan nating ipatupad ang tatlong utos: StartCommand, StopCommand at UnknownCommand. Kailangan natin ang mga ito upang magkaroon tayo ng mapupuno sa ating lalagyan para sa mga utos. Sa ngayon, ang mga teksto ay magiging tuyo at hindi nagbibigay-kaalaman; para sa mga layunin ng gawaing ito, hindi ito napakahalaga. Kaya, StartCommand:
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);
   }
}
Mangyaring basahin nang mabuti ang mga komento bago ang taga-disenyo. Maaaring mangyari ang circular dependency ( circular dependency ) dahil sa isang arkitektura na hindi masyadong tama. Sa aming kaso, sisiguraduhin naming gumagana at tama ang lahat. Ang tunay na bagay mula sa Konteksto ng Application ay idaragdag kapag gumagawa ng command na nasa CommandContainer na. StopCommand:
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);
   }
}
At UnknownCommand. Bakit kailangan natin ito? Para sa amin, ito ay isang mahalagang utos na tutugon kung hindi namin mahanap ang utos na ibinigay sa amin. Kakailanganin din namin ang NoCommand at HelpCommand.
  • NoCommand - magiging responsable para sa sitwasyon kapag ang mensahe ay hindi nagsisimula sa isang utos;
  • Ang HelpCommand ay magiging gabay para sa user, isang uri ng dokumentasyon.
Idagdag natin ang 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);
   }
}
NoCommand:
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);
   }
}
At para sa gawaing ito mayroon pa ring 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);
   }
}
Susunod, magdagdag tayo ng lalagyan para sa ating mga utos. Iimbak nito ang aming mga command object at kapag hiniling ay inaasahan naming matanggap ang kinakailangang command. Tawagin natin itong 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);
   }

}
Tulad ng nakikita mo, ang lahat ay ginawa nang simple. Mayroon kaming isang hindi nababagong mapa na may isang susi sa anyo ng isang halaga ng command at isang halaga sa anyo ng isang command object ng uri ng Command. Sa constructor, pinupunan namin ang hindi nababagong mapa nang isang beses at ina-access ito sa buong operasyon ng application. Ang pangunahing at tanging paraan para sa pagtatrabaho sa lalagyan ay retrieveCommand(String commandIdentifier) ​​​​. Mayroong isang command na tinatawag na UnknownCommand, na responsable para sa mga kaso kapag hindi namin mahanap ang kaukulang command. Ngayon ay handa na kaming ipatupad ang lalagyan sa aming bot class - sa JavaRushTelegramBot: Ito ang hitsura ngayon ng aming bot class:
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;
   }
}
At iyon na nga, tapos na ang mga pagbabago sa code. Paano ko ito masusuri? Kailangan mong ilunsad ang bot at suriin kung gumagana ang lahat. Upang gawin ito, ina-update ko ang token sa application.properties, itakda ang tama, at ilulunsad ang application sa klase ng JavarushTelegramBotApplication: "Proyekto ng Java mula A hanggang Z": Pagpapatupad ng Command Pattern para sa pagtatrabaho sa isang bot.  Bahagi 1 - 3Ngayon kailangan nating suriin kung gumagana ang mga utos gaya ng inaasahan. Sinusuri ko ito nang hakbang-hakbang:
  • StopCommand;
  • StartCommand;
  • HelpCommand;
  • WalangUtos;
  • UnknownCommand.
Narito kung ano ang nangyari: "Proyekto ng Java mula A hanggang Z": Pagpapatupad ng Command Pattern para sa pagtatrabaho sa isang bot.  Bahagi 1 - 4Ang bot ay gumana nang eksakto tulad ng aming inaasahan. Ipinagpatuloy sa pamamagitan ng link .

Ang isang listahan ng lahat ng mga materyales sa serye ay nasa simula ng artikulong ito.

Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION