JavaRush /مدونة جافا /Random-AR /دعونا ننفذ نمط الأوامر للعمل مع الروبوت. (الجزء الأول) - ...
Roman Beekeeper
مستوى

دعونا ننفذ نمط الأوامر للعمل مع الروبوت. (الجزء الأول) - "مشروع جافا من الألف إلى الياء"

نشرت في المجموعة
مرحبا بالجميع أيها الأصدقاء الأعزاء. اليوم سوف نقوم بتنفيذ قالب (القالب هو نمط، في سياقنا هو نفس الشيء) لتصميم الأوامر لاحتياجاتنا. باستخدام هذا القالب، سنعمل بشكل ملائم وصحيح على معالجة أوامر الروبوت الخاص بنا. "مشروع Java من الألف إلى الياء": تنفيذ نمط الأوامر للعمل مع الروبوت.  الجزء 1 - 1
أيها الأصدقاء، هل أعجبكم مشروع Javarush Telegram Bot ؟ لا تكن كسولًا: أعطه نجمة . بهذه الطريقة سيكون من الواضح أنه مثير للاهتمام، وسيكون تطويره أكثر متعة!
في البداية، سيكون من الجيد التحدث عن نوع هذا النمط - الأمر. ولكن إذا قمت بذلك، فإن المقال سيكون كبيرًا جدًا ومرهقًا. لذلك اخترت مواد للدراسة الذاتية:
  1. هذه مقالتي منذ 4 سنوات. لقد كتبتها عندما كنت صغيرًا، لذا لا تحكموا عليها بقسوة.
  2. فيديو لسويدي عاطفي وتفاعلي للغاية على موقع يوتيوب. انا اوصي بشده به. يتحدث بشكل جميل، ولغته الإنجليزية واضحة ومفهومة. وبشكل عام، لديه فيديو عن أنماط التصميم الأخرى.
  3. في التعليقات على مقالتي، أوصى أحد الأشخاص Nullptr35 بهذا الفيديو .
يجب أن يكون هذا كافيًا لتنغمس في الموضوع وتكون على نفس الصفحة مثلي. حسنًا، يمكن لأولئك الذين هم على دراية بنمط التصميم هذا تخطيه والمضي قدمًا بأمان.

نكتب JRTB-3

كل شيء هو نفسه كما كان من قبل:
  1. نقوم بتحديث الفرع الرئيسي.
  2. بناءً على الفرع الرئيسي المحدث، قمنا بإنشاء JRTB-3 جديد .
  3. دعونا ننفذ النمط.
  4. نقوم بإنشاء التزام جديد يصف العمل المنجز.
  5. نقوم بإنشاء طلب سحب، والتحقق منه، وإذا كان كل شيء على ما يرام، فإننا ندمج عملنا.
لن أعرض النقاط 1-2: لقد وصفتها بعناية شديدة في المقالات السابقة، لذلك دعونا ننتقل مباشرة إلى تنفيذ القالب. لماذا هذا القالب مناسب لنا؟ نعم، لأنه في كل مرة ننفذ فيها أمرًا، سننتقل إلى طريقة onUpdateReceived (تحديث التحديث) ، واعتمادًا على الأمر، سننفذ منطقًا مختلفًا. بدون هذا النمط، سيكون لدينا مجموعة كاملة من عبارات 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؛ وبمجرد العثور عليه، نقوم بتشغيل الأمر. وهذا كل شيء...) إذا كانت لديك الرغبة والوقت، فيمكنك تنفيذ العمل مع فرق، أولاً في فصل واحد مرة واحدة، مع مجموعة من الشروط وكل ذلك، ثم باستخدام القالب. سترى الفرق. ما الجمال سيكون! أولاً، لنقم بإنشاء حزمة بجوار حزمة الروبوت، والتي سيتم استدعاؤها بالأمر . "مشروع Java من الألف إلى الياء": تنفيذ نمط الأوامر للعمل مع الروبوت.  الجزء 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);
}
في هذه المرحلة، لا نحتاج إلى تنفيذ العملية العكسية للأمر، لذلك سنقوم بتخطي هذه الطريقة (غير قابلة للتنفيذ). في طريقة التنفيذ، يأتي كائن التحديث كوسيطة - بالضبط تلك التي تأتي إلى طريقتنا الرئيسية في الروبوت. سيحتوي هذا الكائن على كل ما هو مطلوب لمعالجة الأمر. بعد ذلك، سنضيف تعدادًا يخزن قيم الأمر (بدء، إيقاف، وما إلى ذلك). لماذا نحتاج هذا؟ بحيث يكون لدينا مصدر واحد فقط للحقيقة لأسماء الفرق. نقوم أيضًا بإنشائه في حزمة الأوامر الخاصة بنا . دعنا نسميها 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. نحن في حاجة إليها حتى يكون لدينا ما يملأ حاوية الأوامر الخاصة بنا. في الوقت الحالي، ستكون النصوص جافة وغير غنية بالمعلومات، ولأغراض هذه المهمة، فإن هذا ليس مهمًا جدًا. لذا، أمر البدء:
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. أمر الإيقاف:
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);
   }
}
والأمر غير معروف. لماذا نحتاجه؟ بالنسبة لنا، هذا أمر مهم سنستجيب إذا لم نتمكن من العثور على الأمر الذي أعطي لنا. سنحتاج أيضًا إلى NoCommand وHelpCommand.
  • NoCommand - سيكون مسؤولاً عن الموقف عندما لا تبدأ الرسالة بأمر على الإطلاق؛
  • سيكون 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. في المُنشئ، نقوم بملء الخريطة غير القابلة للتغيير مرة واحدة والوصول إليها طوال عملية تشغيل التطبيق. الطريقة الرئيسية والوحيدة للعمل مع الحاوية هي استردادCommand(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: "مشروع Java من الألف إلى الياء": تنفيذ نمط الأوامر للعمل مع الروبوت.  الجزء 1 - 3نحتاج الآن إلى التحقق من أن الأوامر تعمل كما هو متوقع. أتحقق من ذلك خطوة بخطوة:
  • StopCommand;
  • StartCommand;
  • أمر التعليمات;
  • لا يوجد أمر؛
  • طلب مجهول.
وإليك ما حدث: "مشروع Java من الألف إلى الياء": تنفيذ نمط الأوامر للعمل مع الروبوت.  الجزء 1 - 4عمل الروبوت تمامًا كما توقعنا. تابع عبر الرابط .

توجد قائمة بجميع المواد الموجودة في السلسلة في بداية هذه المقالة.

تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION