JavaRush /בלוג Java /Random-HE /בואו ליישם את תבנית הפקודה לעבודה עם הבוט. (חלק 1) - "פרו...
Roman Beekeeper
רָמָה

בואו ליישם את תבנית הפקודה לעבודה עם הבוט. (חלק 1) - "פרויקט ג'אווה מא' עד ת'"

פורסם בקבוצה
שלום לכולם, חברים יקרים. היום ניישם תבנית (תבנית היא תבנית, בהקשר שלנו זה אותו דבר) של עיצוב Command לצרכים שלנו. באמצעות תבנית זו, נעבוד בצורה נוחה ונכונה עם עיבוד הפקודות של הבוט שלנו. "פרויקט ג'אווה מא' עד ת': הטמעת תבנית פקודה לעבודה עם בוט.  חלק 1 - 1
חברים, האם אתם אוהבים את פרויקט Javarush Telegram Bot ? אל תתעצל: תן לזה כוכב . כך יהיה ברור שהוא מעניין, ויהיה יותר נעים לפתח אותו!
בתור התחלה, זה יהיה טוב לדבר על איזה סוג של דפוס זה - Command. אבל אם אעשה זאת, המאמר יהיה מאוד גדול ומסורבל. לכן, בחרתי חומרים ללימוד עצמי:
  1. זה המאמר שלי מלפני 4 שנים. כתבתי את זה כשהייתי צעיר, אז אל תשפוט את זה בחומרה.
  2. סרטון של שוודי מאוד רגשי ואינטראקטיבי ביוטיוב. אני מאוד ממליץ על זה. הוא מדבר יפה, האנגלית שלו ברורה ומובנת. ובכלל, יש לו סרטון על דפוסי עיצוב אחרים.
  3. בתגובות למאמר שלי, מישהו Nullptr35 המליץ ​​על הסרטון הזה .
זה אמור להספיק כדי לשקוע בנושא ולהיות באותו עמוד כמוני. ובכן, מי שמכיר את דפוס העיצוב הזה יכול לדלג בבטחה ולהמשיך הלאה.

אנו כותבים JRTB-3

הכל אותו דבר כמו קודם:
  1. אנו מעדכנים את הסניף הראשי.
  2. בהתבסס על הסניף הראשי המעודכן, אנו יוצרים JRTB-3 חדש .
  3. בואו ליישם את הדפוס.
  4. אנו יוצרים מחויבות חדשה המתארת ​​את העבודה שנעשתה.
  5. אנו יוצרים בקשת משיכה, בודקים אותה, ואם הכל בסדר, אנו ממזגים את העבודה שלנו.
לא אראה את נקודות 1-2: תיארתי אותן בקפידה רבה במאמרים קודמים, אז בואו נמשיך ישר ליישום התבנית. למה התבנית הזו מתאימה לנו? כן, כי בכל פעם שנבצע פקודה, נעבור לשיטת onUpdateReceived(Update update) ובהתאם לפקודה נבצע לוגיקה שונה. ללא הדפוס הזה, היו לנו שורה שלמה של הצהרות אם-אחר אם. משהו כזה:
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);
}
זה הכל! ולא משנה כמה פקודות נוסיף, קטע הקוד הזה יישאר ללא שינוי. מה הוא עושה? אם הראשון מוודא שההודעה מתחילה בקידומת הפקודה "/". אם זה המקרה, אז אנחנו בוחרים את השורה עד לרווח הראשון ומחפשים את הפקודה המתאימה ב-CommandContainer; ברגע שנמצא אותה, אנו מפעילים את הפקודה. וזה הכל...) אם יש לך רצון וזמן, אתה יכול ליישם עבודה עם צוותים, תחילה בכיתה אחת בבת אחת, עם שלל תנאים וכל זה, ולאחר מכן באמצעות תבנית. אתה תראה את ההבדל. איזה יופי זה יהיה! ראשית, בואו ניצור חבילה ליד חבילת הבוט, שתיקרא command . "פרויקט ג'אווה מא' עד ת': הטמעת תבנית פקודה לעבודה עם בוט.  חלק 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);
}
בשלב זה, אנחנו לא צריכים ליישם את הפעולה ההפוכה של הפקודה, אז נדלג על שיטה זו (בטל ביצוע). בשיטת execute האובייקט Update מגיע כארגומנט - בדיוק זה שמגיע למתודה הראשית שלנו בבוט. אובייקט זה יכיל את כל הדרוש לעיבוד הפקודה. לאחר מכן, נוסיף Enum שיאחסן את ערכי הפקודה (התחלה, עצירה וכן הלאה). למה אנחנו צריכים את זה? כך שיש לנו רק מקור אחד של אמת לשמות הצוותים. אנו יוצרים אותו גם בחבילת הפקודה שלנו . בואו נקרא לזה 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 (בשירותי שמות הם בדרך כלל מוסיפים שירות בסוף השם):
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. אנחנו צריכים אותם כדי שיהיה לנו משהו למלא את המיכל שלנו לפקודות. לעת עתה, הטקסטים יהיו יבשים ולא אינפורמטיביים; למטרות משימה זו, זה לא מאוד חשוב. אז, 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);
   }
}
אנא קרא את ההערות בעיון לפני המעצב. תלות מעגלית ( תלות מעגלית ) יכולה להתרחש עקב ארכיטקטורה שאינה ממש נכונה. במקרה שלנו נוודא שהכל עובד ותקין. האובייקט האמיתי מהקשר היישום יתווסף בעת יצירת הפקודה כבר ב-CommandContainer. 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);
   }
}
ו-UnknownCommand. למה אנחנו צריכים את זה? עבורנו זו פקודה חשובה שתגיב אם לא נוכל למצוא את הפקודה שניתנה לנו. נצטרך גם NoCommand ו- HelpCommand.
  • NoCommand - יהיה אחראי למצב שבו ההודעה לא מתחילה בפקודה כלל;
  • HelpCommand יהווה מדריך למשתמש, מעין תיעוד.
בואו נוסיף 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: "פרויקט ג'אווה מא' עד ת': הטמעת תבנית פקודה לעבודה עם בוט.  חלק 1 - 3כעת עלינו לבדוק שהפקודות פועלות כמצופה. אני בודק את זה שלב אחר שלב:
  • StopCommand;
  • StartCommand;
  • HelpCommand;
  • אין תגובה;
  • פקודה לא מזוהה.
זה מה שקרה: "פרויקט ג'אווה מא' עד ת': הטמעת תבנית פקודה לעבודה עם בוט.  חלק 1 - 4הבוט עבד בדיוק כפי שציפינו. המשך דרך קישור .

רשימה של כל החומרים בסדרה נמצאת בתחילת מאמר זה.

הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION