JavaRush /Blog Java /Random-FR /Implémentons le modèle de commande pour travailler avec l...
Roman Beekeeper
Niveau 35

Implémentons le modèle de commande pour travailler avec le bot. (Partie 1) - "Projet Java de A à Z"

Publié dans le groupe Random-FR
Bonjour à tous, chers amis. Aujourd'hui, nous allons implémenter un modèle (un modèle est un modèle, dans notre contexte c'est la même chose) de conception de commandes pour nos besoins. En utilisant ce modèle, nous travaillerons facilement et correctement avec le traitement des commandes de notre bot. "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 1 - 1
Les amis, aimez-vous le projet Javarush Telegram Bot ? Ne soyez pas paresseux : donnez-lui une étoile . De cette façon, il sera clair qu'il est intéressant, et il sera plus agréable de le développer !
Pour commencer, il serait bon de parler de quel type de modèle il s'agit - Commandement. Mais si je fais cela, l’article sera très volumineux et encombrant. Par conséquent, j'ai choisi du matériel pour l'auto-apprentissage :
  1. Ceci est mon article d'il y a 4 ans. Je l’ai écrit quand j’étais junior, alors ne le jugez pas trop sévèrement.
  2. Vidéo d'un Suédois très émouvant et interactif sur YouTube. Je le recommande fortement. Il parle magnifiquement, son anglais est clair et compréhensible. Et en général, il a une vidéo sur d'autres modèles de conception.
  3. Dans les commentaires de mon article, quelqu'un Nullptr35 a recommandé cette vidéo .
Cela devrait suffire pour vous immerger dans le sujet et être sur la même longueur d’onde que moi. Eh bien, ceux qui connaissent ce modèle de conception peuvent sauter et passer à autre chose en toute sécurité.

Nous écrivons JRTB-3

Tout est comme avant :
  1. Nous mettons à jour la branche principale.
  2. Sur la base de la branche principale mise à jour, nous créons un nouveau JRTB-3 .
  3. Implémentons le modèle.
  4. Nous créons un nouveau commit décrivant le travail effectué.
  5. Nous créons une pull request, la vérifions et si tout va bien, nous fusionnons notre travail.
Je ne montrerai pas les points 1-2 : je les ai décrits très attentivement dans les articles précédents, passons donc directement à la mise en œuvre du modèle. Pourquoi ce modèle nous convient-il ? Oui, car chaque fois que nous exécuterons une commande, nous accéderons à la méthode onUpdateReceived(Update update) et, en fonction de la commande, nous exécuterons une logique différente. Sans ce modèle, nous aurions toute une série d’instructions if-else if. Quelque chose comme ça:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
De plus, là où il y a des points de suspension, il peut y avoir plusieurs dizaines d'équipes supplémentaires. Et comment gérer cela normalement ? Comment soutenir ? Difficile et difficile. Cela signifie que cette option ne nous convient pas. Ça devrait ressembler a quelque chose comme ca:
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);
}
C'est tout! Et quel que soit le nombre de commandes que nous ajoutons, cette section de code restera inchangée. Que fait-il? Le premier if garantit que le message commence par le préfixe de commande "/". Si tel est le cas, alors nous sélectionnons la ligne jusqu'au premier espace et cherchons la commande correspondante dans le CommandContainer ; dès que nous la trouvons, nous exécutons la commande. Et c'est tout...) Si vous en avez l'envie et le temps, vous pouvez mettre en œuvre le travail en équipe, d'abord dans une classe à la fois, avec un tas de conditions et tout ça, puis en utilisant un modèle. Vous verrez la différence. Quelle beauté ce sera ! Tout d'abord, créons un package à côté du package bot, qui sera appelé command . "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 1 - 2Et déjà dans ce package il y aura toutes les classes liées à l'implémentation de la commande. Nous avons besoin d'une interface pour travailler avec les commandes. Dans ce cas, créons-le :
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);
}
À ce stade, nous n'avons pas besoin d'implémenter l'opération inverse de la commande, nous allons donc ignorer cette méthode (annuler l'exécution). Dans la méthode d'exécution, l'objet Update est présenté comme un argument - exactement celui qui vient à notre méthode principale dans le bot. Cet objet contiendra tout le nécessaire pour traiter la commande. Ensuite, nous ajouterons une énumération qui stockera les valeurs de la commande (démarrer, arrêter, etc.). Pourquoi avons nous besoin de ça? Nous n’avons donc qu’une seule source de vérité pour les noms des équipes. Nous le créons également dans notre package de commandes . Appelons-le 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;
   }

}
Nous avons également besoin d'un service qui enverra des messages via un bot. Pour ce faire, nous créerons un package de services à côté du package de commandes , auquel nous ajouterons tous les services nécessaires. Ici, il convient de se concentrer sur ce que j'entends par le mot service dans ce cas. Si l'on considère une application, elle est souvent divisée en plusieurs couches : une couche pour travailler avec les points finaux - les contrôleurs, une couche de logique métier - les services et une couche pour travailler avec la base de données - un référentiel. Par conséquent, dans notre cas, un service est une classe qui implémente une sorte de logique métier. Comment créer correctement un service ? Tout d’abord, créez une interface pour celui-ci et une implémentation. Ajoutez l'implémentation à l'aide de l'annotation `@Service` au contexte d'application de notre application SpringBoot et, si nécessaire, resserrez-la à l'aide de l'annotation `@Autowired`. Par conséquent, nous créons l'interface SendBotMessageService (dans la dénomination des services, ils ajoutent généralement Service à la fin du nom) :
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);
}
Ensuite, nous créons son implémentation :
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();
       }
   }
}
Voici à quoi ressemble la mise en œuvre. La magie la plus importante réside dans l’endroit où le designer est créé. En utilisant l'annotation @Autowired sur le constructeur, SpringBoot recherchera un objet de cette classe dans son contexte d'application. Et il est déjà là. Cela fonctionne comme ceci : dans notre application, partout où nous pouvons accéder au bot et faire quelque chose. Et ce service est responsable de l'envoi des messages. Pour ne pas écrire quelque chose comme ça à chaque fois et partout :
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();
}
Nous avons déplacé cette logique dans une classe distincte et l'utiliserons si nécessaire. Nous devons maintenant implémenter trois commandes : StartCommand, StopCommand et UnknownCommand. Nous en avons besoin pour avoir de quoi remplir notre conteneur de commandes. Pour l’instant, les textes seront arides et peu informatifs ; pour les besoins de cette tâche, cela n’est pas très important. Donc, 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);
   }
}
Veuillez lire attentivement les commentaires avant le concepteur. La dépendance circulaire ( dépendance circulaire ) peut se produire en raison d'une architecture qui n'est pas tout à fait correcte. Dans notre cas, nous veillerons à ce que tout fonctionne et soit correct. L'objet réel du contexte d'application sera ajouté lors de la création de la commande déjà dans le CommandContainer. Commande d'arrêt :
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);
   }
}
Et UnknownCommand. Pourquoi en avons-nous besoin? Pour nous, c'est une commande importante qui répondra si nous ne parvenons pas à retrouver la commande qui nous a été donnée. Nous aurons également besoin de NoCommand et HelpCommand.
  • NoCommand - sera responsable de la situation dans laquelle le message ne commence pas du tout par une commande ;
  • HelpCommand sera un guide pour l'utilisateur, une sorte de documentation.
Ajoutons 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);
   }
}
Aucune commande:
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);
   }
}
Et pour cette tâche, il existe toujours 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);
   }
}
Ensuite, ajoutons un conteneur pour nos commandes. Il stockera nos objets de commande et sur demande, nous espérons recevoir la commande requise. Appelons-le 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);
   }

}
Comme vous pouvez le constater, tout a été fait simplement. Nous avons une carte immuable avec une clé sous la forme d'une valeur de commande et une valeur sous la forme d'un objet de commande de type Command. Dans le constructeur, nous remplissons une seule fois la carte immuable et y accédons tout au long du fonctionnement de l’application. La méthode principale et unique pour travailler avec le conteneur est retrieveCommand(String commandIdentifier) ​​​​​​. Il existe une commande appelée UnknownCommand, qui est responsable des cas où nous ne trouvons pas la commande correspondante. Nous sommes maintenant prêts à implémenter le conteneur dans notre classe de bot - dans JavaRushTelegramBot : voici à quoi ressemble maintenant notre classe de bot :
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;
   }
}
Et voilà, les modifications du code sont terminées. Comment puis-je vérifier cela ? Vous devez lancer le bot et vérifier que tout fonctionne. Pour ce faire, je mets à jour le jeton dans application.properties, définis le bon et lance l'application dans la classe JavarushTelegramBotApplication : "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 1 à 3nous devons maintenant vérifier que les commandes fonctionnent comme prévu. Je vérifie étape par étape :
  • Commande d'arrêt ;
  • CommandeDémarrer ;
  • AideCommand ;
  • Aucune commande;
  • Commande inconnue.
Voici ce qui s'est passé : "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 1 à 4le bot a fonctionné exactement comme prévu. Suite via lien .

Une liste de tous les matériaux de la série se trouve au début de cet article.

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