JavaRush /Blog Java /Random-ES /Implementemos el patrón de comando para trabajar con el b...

Implementemos el patrón de comando para trabajar con el bot. (Parte 1) - "Proyecto Java de la A a la Z"

Publicado en el grupo Random-ES
Hola a todos, queridos amigos. Hoy implementaremos una plantilla (plantilla es un patrón, en nuestro contexto es lo mismo) de diseño de Command para nuestras necesidades. Con esta plantilla trabajaremos de forma cómoda y correcta con el procesamiento de los comandos de nuestro bot. "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 1 - 1
Amigos, ¿les gusta el proyecto Javarush Telegram Bot ? No seas perezoso: dale una estrella . ¡De esta manera quedará claro que es interesante y será más agradable desarrollarlo!
Para empezar, sería bueno hablar sobre qué tipo de patrón es este: Comando. Pero si hago esto, el artículo será muy extenso y engorroso. Por eso, elegí materiales para el autoaprendizaje:
  1. Este es mi artículo de hace 4 años. Lo escribí cuando era joven, así que no lo juzgues con demasiada dureza.
  2. Vídeo de un sueco muy emotivo e interactivo en YouTube. Lo recomiendo altamente. Habla maravillosamente, su inglés es claro y comprensible. Y en general tiene un vídeo sobre otros patrones de diseño.
  3. En los comentarios a mi artículo, alguien Nullptr35 recomendó este video .
Esto debería ser suficiente para sumergirse en el tema y estar en la misma página que yo. Bueno, aquellos que estén familiarizados con este patrón de diseño pueden saltarlo y seguir adelante con seguridad.

Escribimos JRTB-3

Todo sigue igual que antes:
  1. Actualizamos la rama principal.
  2. Basándonos en la rama principal actualizada, creamos un nuevo JRTB-3 .
  3. Implementemos el patrón.
  4. Creamos un nuevo compromiso que describe el trabajo realizado.
  5. Creamos un pull request, lo comprobamos y si todo está bien fusionamos nuestro trabajo.
No mostraré los puntos 1 y 2: los describí con mucho cuidado en artículos anteriores, así que pasemos directamente a implementar la plantilla. ¿Por qué esta plantilla es adecuada para nosotros? Sí, porque cada vez que ejecutemos un comando, iremos al método onUpdateReceived(Update update) , y dependiendo del comando ejecutaremos una lógica diferente. Sin este patrón, tendríamos una gran cantidad de declaraciones if-else if. Algo como esto:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
Además, donde hay puntos suspensivos, puede haber varias docenas de equipos más. ¿Y cómo manejar esto normalmente? ¿Cómo apoyar? Difícil y difícil. Esto significa que esta opción no nos conviene. Debería verse así:
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);
}
¡Eso es todo! Y no importa cuántos comandos agreguemos, esta sección de código permanecerá sin cambios. ¿Qué está haciendo? El primero si se asegura de que el mensaje comience con el prefijo de comando "/". Si este es el caso, entonces seleccionamos la línea hasta el primer espacio y buscamos el comando correspondiente en el CommandContainer, en cuanto lo encontremos ejecutamos el comando. Y eso es todo...) Si tienes ganas y tiempo, puedes implementar el trabajo en equipo, primero en una clase a la vez, con un montón de condiciones y todo eso, y luego usando una plantilla. Verás la diferencia. ¡Qué belleza será! Primero, creemos un paquete junto al paquete del bot, que se llamará comando . "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 1 - 2Y ya en este paquete estarán todas las clases relacionadas con la implementación del comando. Necesitamos una interfaz para trabajar con comandos. Para este caso, vamos a crearlo:
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);
}
En este punto, no necesitamos implementar la operación inversa del comando, por lo que omitiremos este método (no ejecutaremos). En el método de ejecución, el objeto Actualizar viene como argumento , exactamente el que viene a nuestro método principal en el bot. Este objeto contendrá todo lo necesario para procesar el comando. A continuación, agregaremos una enumeración que almacenará los valores del comando (inicio, parada, etc.). ¿Porqué necesitamos esto? De modo que solo tenemos una fuente de verdad para los nombres de los equipos. También lo creamos en nuestro paquete de comandos . Llamémoslo NombreComando :
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;
   }

}
También necesitamos un servicio que envíe mensajes a través de un bot. Para ello crearemos un paquete de servicios junto al paquete de comandos , al que añadiremos todos los servicios necesarios. Aquí vale la pena centrarse en lo que quiero decir con la palabra servicio en este caso. Si consideramos una aplicación, a menudo se divide en varias capas: una capa para trabajar con puntos finales (controladores), una capa de lógica empresarial (servicios) y una capa para trabajar con una base de datos: un repositorio. Por tanto, en nuestro caso, un servicio es una clase que implementa algún tipo de lógica empresarial. ¿Cómo crear un servicio correctamente? Primero, cree una interfaz para ello y una implementación. Agregue la implementación usando la anotación `@Service` al contexto de aplicación de nuestra aplicación SpringBoot y, si es necesario, ajústela usando la anotación `@Autowired`. Por lo tanto, creamos la interfaz SendBotMessageService (al nombrar servicios generalmente agregan Servicio al final del nombre):
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);
}
A continuación, creamos su implementación:
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();
       }
   }
}
Así es como se ve la implementación. La magia más importante es donde se crea el diseñador. Usando la anotación @Autowired en el constructor, SpringBoot buscará un objeto de esta clase en su contexto de aplicación. Y él ya está allí. Funciona así: en nuestra aplicación, en cualquier lugar podemos acceder al bot y hacer algo. Y este servicio se encarga de enviar mensajes. Para que no escribamos algo como esto cada vez en cada 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();
}
Hemos trasladado esta lógica a una clase separada y la usaremos si es necesario. Ahora necesitamos implementar tres comandos: StartCommand, StopCommand y UnknownCommand. Los necesitamos para tener algo con qué llenar nuestro contenedor de comandos. Por ahora, los textos serán secos y poco informativos; para los propósitos de esta tarea, esto no es muy importante. Entonces, IniciarComando:
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.
   // Потому что если это сделать так, то будет циклическая зависимость, которая
   // ломает работу aplicaciones.
   public StartCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), START_MESSAGE);
   }
}
Por favor lea atentamente los comentarios antes que el diseñador. La dependencia circular ( dependencia circular ) puede ocurrir debido a una arquitectura que no es del todo correcta. En nuestro caso nos aseguraremos de que todo funciona y está correcto. El objeto real del contexto de la aplicación se agregará al crear el comando que ya está en el CommandContainer. Comando Detener:
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);
   }
}
Y comando desconocido. ¿Por qué lo necesitamos? Para nosotros, este es un comando importante que responderá si no podemos encontrar el comando que nos fue dado. También necesitaremos NoCommand y HelpCommand.
  • NoCommand: será responsable de la situación en la que el mensaje no comienza con ningún comando;
  • HelpCommand será una guía para el usuario, una especie de documentación.
Agreguemos 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);
   }
}
Sin comando:
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"
           + "Quéбы посмотреть список команд введите /help";

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

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), NO_MESSAGE);
   }
}
Y para esta tarea todavía existe 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);
   }
}
A continuación, agreguemos un contenedor para nuestros comandos. Almacenará nuestros objetos de comando y, previa solicitud, esperamos recibir el comando requerido. Llamémoslo 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);
   }

}
Como puede ver, todo se hizo de forma sencilla. Tenemos un mapa inmutable con una clave en forma de valor de comando y un valor en forma de objeto de comando de tipo Comando. En el constructor, completamos el mapa inmutable una vez y accedemos a él durante la operación de la aplicación. El método principal y único para trabajar con el contenedor es retrieveCommand(String commandIdentifier) ​​​​. Existe un comando llamado UnknownCommand, que se encarga de los casos en los que no podemos encontrar el comando correspondiente. Ahora estamos listos para implementar el contenedor en nuestra clase de bot, en JavaRushTelegramBot: Así es como se ve ahora nuestra clase 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;
   }
}
Y listo, se completan los cambios en el código. ¿Cómo puedo comprobar esto? Debes iniciar el bot y comprobar que todo funciona. Para hacer esto, actualizo el token en application.properties, configuro el correcto y ejecuto la aplicación en la clase JavarushTelegramBotApplication: "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 1 - 3ahora debemos verificar que los comandos funcionen como se esperaba. Lo reviso paso a paso:
  • Comando Detener;
  • Comando de inicio;
  • Comando de ayuda;
  • Sin comando;
  • Comando desconocido.
Esto es lo que sucedió: "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 1 - 4el bot funcionó exactamente como esperábamos. Continúa mediante enlace .

Al principio de este artículo encontrará una lista de todos los materiales de la serie.

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