JavaRush /Java-Blog /Random-DE /Lassen Sie uns das Befehlsmuster implementieren, um mit d...

Lassen Sie uns das Befehlsmuster implementieren, um mit dem Bot zu arbeiten. (Teil 1) - „Java-Projekt von A bis Z“

Veröffentlicht in der Gruppe Random-DE
Hallo zusammen, liebe Freunde. Heute werden wir eine Vorlage (Vorlage ist ein Muster, in unserem Kontext ist es dasselbe) des Befehlsdesigns für unsere Bedürfnisse implementieren. Mit dieser Vorlage können wir die Befehle unseres Bots bequem und korrekt verarbeiten. „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 1 - 1
Freunde, gefällt euch das Javarush Telegram Bot-Projekt ? Seien Sie nicht faul: Geben Sie ihm einen Stern . Auf diese Weise wird deutlich, dass er interessant ist, und es wird angenehmer sein, ihn weiterzuentwickeln!
Zunächst wäre es gut, darüber zu sprechen, was für ein Muster das ist – Befehl. Aber wenn ich das mache, wird der Artikel sehr groß und umständlich. Deshalb habe ich Materialien zum Selbststudium ausgewählt:
  1. Dies ist mein Artikel von vor 4 Jahren. Ich habe es geschrieben, als ich noch ein Junior war, also beurteilen Sie es nicht zu hart.
  2. Video eines sehr emotionalen und interaktiven Schweden auf YouTube. Ich empfehle es sehr. Er spricht wunderbar, sein Englisch ist klar und verständlich. Und im Allgemeinen hat er ein Video über andere Designmuster.
  3. In den Kommentaren zu meinem Artikel hat jemand Nullptr35 dieses Video empfohlen .
Dies sollte ausreichen, um in das Thema einzutauchen und mit mir auf einer Wellenlänge zu sein. Nun, diejenigen, die mit diesem Designmuster vertraut sind, können es getrost überspringen und weitermachen.

Wir schreiben JRTB-3

Alles ist wie zuvor:
  1. Wir aktualisieren den Hauptzweig.
  2. Basierend auf dem aktualisierten Hauptzweig erstellen wir einen neuen JRTB-3 .
  3. Lassen Sie uns das Muster implementieren.
  4. Wir erstellen einen neuen Commit, der die geleistete Arbeit beschreibt.
  5. Wir erstellen einen Pull-Request, überprüfen ihn und wenn alles in Ordnung ist, führen wir unsere Arbeit zusammen.
Ich werde die Punkte 1-2 nicht zeigen: Ich habe sie in früheren Artikeln sehr ausführlich beschrieben, also lasst uns direkt mit der Implementierung der Vorlage fortfahren. Warum ist diese Vorlage für uns geeignet? Ja, denn jedes Mal, wenn wir einen Befehl ausführen, gehen wir zur Methode onUpdateReceived(Update update) und führen je nach Befehl unterschiedliche Logik aus. Ohne dieses Muster hätten wir eine ganze Reihe von if-else if-Anweisungen. Etwas wie das:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
Darüber hinaus kann es dort, wo Auslassungspunkte vorhanden sind, mehrere Dutzend weitere Teams geben. Und wie gehe ich normal damit um? Wie unterstützen? Schwierig und schwierig. Das bedeutet, dass diese Option für uns nicht passt. Es sollte ungefähr so ​​aussehen:
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);
}
Und alle! Und egal wie viele Befehle wir hinzufügen, dieser Codeabschnitt bleibt unverändert. Was macht er? Das erste if stellt sicher, dass die Nachricht mit dem Befehlspräfix „/“ beginnt. Ist dies der Fall, dann markieren wir die Zeile bis zum ersten Leerzeichen und suchen im CommandContainer nach dem entsprechenden Befehl; sobald wir ihn gefunden haben, führen wir den Befehl aus. Und das ist alles...) Wenn Sie Lust und Zeit haben, können Sie die Arbeit mit Teams zunächst in einer Klasse gleichzeitig, mit einer Reihe von Bedingungen und all dem, und dann mithilfe einer Vorlage umsetzen. Sie werden den Unterschied sehen. Was für eine Schönheit wird es sein! Erstellen wir zunächst ein Paket neben dem Bot-Paket mit dem Namen command . „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 1 - 2Und bereits in diesem Paket sind alle Klassen enthalten, die sich auf die Implementierung des Befehls beziehen. Wir benötigen eine Schnittstelle für die Arbeit mit Befehlen. Für diesen Fall erstellen wir es:
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);
}
Zu diesem Zeitpunkt müssen wir die umgekehrte Operation des Befehls nicht implementieren, daher überspringen wir diese Methode (nicht ausführen). In der Execute-Methode kommt das Update- Objekt als Argument – ​​genau das, das zu unserer Hauptmethode im Bot kommt. Dieses Objekt enthält alles, was zur Verarbeitung des Befehls erforderlich ist. Als Nächstes fügen wir eine Enumeration hinzu, die die Befehlswerte (Start, Stopp usw.) speichert. Warum brauchen wir das? Damit wir nur eine Quelle der Wahrheit für Teamnamen haben. Wir erstellen es auch in unserem Befehlspaket . Nennen wir es 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;
   }

}
Wir brauchen auch einen Dienst, der Nachrichten über einen Bot sendet. Dazu erstellen wir neben dem Befehlspaket ein Servicepaket , zu dem wir alle notwendigen Services hinzufügen. Hier lohnt es sich, sich darauf zu konzentrieren, was ich in diesem Fall unter dem Wort Service verstehe. Wenn wir eine Anwendung betrachten, ist sie oft in mehrere Schichten unterteilt: eine Schicht für die Arbeit mit Endpunkten – Controller, eine Schicht der Geschäftslogik – Dienste, und eine Schicht für die Arbeit mit der Datenbank – ein Repository. Daher ist ein Dienst in unserem Fall eine Klasse, die eine Art Geschäftslogik implementiert. Wie erstellt man einen Dienst richtig? Erstellen Sie zunächst eine Schnittstelle dafür und eine Implementierung. Fügen Sie die Implementierung mithilfe der Annotation „@Service“ zum Anwendungskontext unserer SpringBoot-Anwendung hinzu und straffen Sie sie bei Bedarf mithilfe der Annotation „@Autowired“. Deshalb erstellen wir die SendBotMessageService-Schnittstelle (bei der Benennung von Diensten fügen sie normalerweise Service am Ende des Namens hinzu):
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);
}
Als nächstes erstellen wir seine Implementierung:
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();
       }
   }
}
So sieht die Umsetzung aus. Die wichtigste Magie liegt dort, wo der Designer geschaffen wird. Mithilfe der @Autowired-Annotation im Konstruktor sucht SpringBoot in seinem Anwendungskontext nach einem Objekt dieser Klasse. Und er ist schon da. Das funktioniert so: In unserer Anwendung können wir überall auf den Bot zugreifen und etwas tun. Und dieser Dienst ist für den Versand von Nachrichten zuständig. Damit wir nicht jedes Mal und an jedem Ort so etwas schreiben:
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();
}
Wir haben diese Logik in eine eigene Klasse verschoben und werden sie bei Bedarf verwenden. Jetzt müssen wir drei Befehle implementieren: StartCommand, StopCommand und UnknownCommand. Wir brauchen sie, damit wir unseren Befehlsbehälter füllen können. Die Texte werden vorerst trocken und wenig informativ sein; für die Zwecke dieser Aufgabe ist dies nicht sehr wichtig. Also, 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.
   // Потому что если это сделать так, то будет циклическая зависимость, которая
   // ломает работу Anwendungen.
   public StartCommand(SendBotMessageService sendBotMessageService) {
       this.sendBotMessageService = sendBotMessageService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), START_MESSAGE);
   }
}
Bitte lesen Sie die Kommentare vor dem Designer sorgfältig durch. Eine zirkuläre Abhängigkeit ( zirkuläre Abhängigkeit ) kann aufgrund einer nicht ganz passenden Architektur auftreten. In unserem Fall sorgen wir dafür, dass alles funktioniert und korrekt ist. Das reale Objekt aus dem Anwendungskontext wird beim Erstellen des Befehls bereits im CommandContainer hinzugefügt. Stoppbefehl:
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);
   }
}
Und UnknownCommand. Warum brauchen wir es? Für uns ist dies ein wichtiger Befehl, der reagiert, wenn wir den uns gegebenen Befehl nicht finden konnten. Wir benötigen außerdem NoCommand und HelpCommand.
  • NoCommand – ist für den Fall verantwortlich, dass die Nachricht überhaupt nicht mit einem Befehl beginnt;
  • HelpCommand wird ein Leitfaden für den Benutzer sein, eine Art Dokumentation.
Fügen wir HelpCommand hinzu:
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);
   }
}
Kein Kommentar:
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"
           + "Wasбы посмотреть список команд введите /help";

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

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), NO_MESSAGE);
   }
}
Und für diese Aufgabe gibt es noch 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);
   }
}
Als nächstes fügen wir einen Container für unsere Befehle hinzu. Es speichert unsere Befehlsobjekte und auf Anfrage erwarten wir den Erhalt des erforderlichen Befehls. Nennen wir es 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);
   }

}
Wie Sie sehen, wurde alles einfach gemacht. Wir haben eine unveränderliche Karte mit einem Schlüssel in Form eines Befehlswerts und einem Wert in Form eines Befehlsobjekts vom Typ Befehl. Im Konstruktor füllen wir die unveränderliche Karte einmal aus und greifen während des gesamten Betriebs der Anwendung darauf zu. Die wichtigste und einzige Methode zum Arbeiten mit dem Container ist „retrieCommand(String commandIdentifier)“ . Es gibt einen Befehl namens UnknownCommand, der für Fälle verantwortlich ist, in denen wir den entsprechenden Befehl nicht finden können. Jetzt sind wir bereit, den Container in unsere Bot-Klasse zu implementieren – in JavaRushTelegramBot: So sieht unsere Bot-Klasse jetzt aus:
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;
   }
}
Und damit sind die Änderungen am Code abgeschlossen. Wie kann ich das überprüfen? Sie müssen den Bot starten und prüfen, ob alles funktioniert. Dazu aktualisiere ich das Token in application.properties, setze das richtige und starte die Anwendung in der JavarushTelegramBotApplication-Klasse: „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 1 - 3Jetzt müssen wir überprüfen, ob die Befehle wie erwartet funktionieren. Ich prüfe es Schritt für Schritt:
  • StopCommand;
  • StartCommand;
  • HelpCommand;
  • Kein Kommentar;
  • Unbekannter Befehl.
Folgendes ist passiert: „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 1 - 4Der Bot funktionierte genau so, wie wir es erwartet hatten. Fortsetzung per Link .

Eine Liste aller Materialien der Serie finden Sie am Anfang dieses Artikels.

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