JavaRush /وبلاگ جاوا /Random-FA /بیایید الگوی فرمان را برای کار با ربات پیاده سازی کنیم. (...
Roman Beekeeper
مرحله

بیایید الگوی فرمان را برای کار با ربات پیاده سازی کنیم. (قسمت 1) - "پروژه جاوا از A تا Z"

در گروه منتشر شد
سلام به همه دوستان عزیز. امروز ما یک قالب (الگو یک الگو است، در زمینه ما همان چیزی است) از طراحی Command را برای نیازهای خود پیاده سازی می کنیم. با استفاده از این الگو، ما به راحتی و به درستی با پردازش دستورات ربات خود کار خواهیم کرد. "پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 1 - 1
دوستان پروژه ربات تلگرام Javarush را دوست دارید ؟ تنبل نباشید: به آن ستاره بدهید . به این ترتیب مشخص می شود که او جالب است و رشد او لذت بخش تر خواهد بود!
برای شروع، خوب است در مورد این که چه نوع الگوی است - Command صحبت کنیم. اما اگر این کار را انجام دهم، مقاله بسیار بزرگ و دست و پا گیر خواهد شد. بنابراین، موادی را برای خودآموزی انتخاب کردم:
  1. این مقاله من مربوط به 4 سال پیش است. زمانی که جوان بودم آن را نوشتم، پس خیلی سخت قضاوت نکنید.
  2. ویدیوی یک سوئدی بسیار احساسی و تعاملی در یوتیوب. من آن را به شدت توصیه می کنم. او به زیبایی صحبت می کند، انگلیسی اش واضح و قابل درک است. و در کل یک ویدیو در مورد دیگر الگوهای طراحی دارد.
  3. در نظرات مقاله من، شخصی Nullptr35 این ویدیو را توصیه کرد .
این باید برای غرق شدن در موضوع کافی باشد و در همان صفحه من باشید. خوب، کسانی که با این الگوی طراحی آشنا هستند، می توانند با خیال راحت از آن بگذرند و ادامه دهند.

ما JRTB-3 می نویسیم

همه چیز مثل قبل است:
  1. ما شعبه اصلی را به روز می کنیم.
  2. بر اساس شاخه اصلی به روز شده، یک JRTB-3 جدید ایجاد می کنیم .
  3. بیایید الگو را پیاده سازی کنیم.
  4. ما یک commit جدید ایجاد می کنیم که کار انجام شده را توصیف می کند.
  5. ما یک درخواست کشش ایجاد می کنیم، آن را بررسی می کنیم و اگر همه چیز خوب بود، کارمان را ادغام می کنیم.
من نکات 1-2 را نشان نمی دهم: آنها را با دقت در مقالات قبلی شرح دادم، بنابراین بیایید مستقیماً به پیاده سازی الگو برویم. چرا این قالب برای ما مناسب است؟ بله، چون هر بار که دستوری را اجرا می کنیم، به روش onUpdateReceived(Update update) می رویم و بسته به دستور، منطق متفاوتی را اجرا می کنیم. بدون این الگو، ما یکسری عبارات if-else if را خواهیم داشت. چیزی شبیه به این:
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 می‌گویند . "پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 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);
}
در این مرحله، ما نیازی به اجرای عملیات معکوس فرمان نداریم، بنابراین از این روش صرفنظر می کنیم (unexecute). در متد 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 را ایجاد می کنیم (در نامگذاری سرویس ها معمولا Service را در انتهای نام اضافه می کنند):
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);
   }
}
لطفا نظرات را قبل از طراح با دقت بخوانید. وابستگی دایره ای ( وابستگی دایره ای ) می تواند به دلیل ساختاری که کاملاً درست نیست رخ دهد. در مورد ما، ما مطمئن خواهیم شد که همه چیز کار می کند و درست است. شی واقعی از Application Context هنگام ایجاد دستور از قبل در 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);
   }
}
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);
   }
}
و برای این کار هنوز 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 راه‌اندازی می‌کنم: "پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 1 - 3اکنون باید بررسی کنیم که دستورات مطابق انتظار عمل می‌کنند. مرحله به مرحله بررسی می کنم:
  • StopCommand؛
  • StartCommand؛
  • HelpCommand؛
  • NoCommand؛
  • دستور ناشناخته.
اتفاقی که افتاد این است: "پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 1 - 4ربات دقیقاً همانطور که انتظار داشتیم کار کرد. ادامه از طریق لینک

فهرستی از تمام مواد این مجموعه در ابتدای این مقاله است.

نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION