JavaRush /Java Blog /Random-ID /Mari terapkan Pola Perintah untuk bekerja dengan bot. (Ba...

Mari terapkan Pola Perintah untuk bekerja dengan bot. (Bagian 1) - "Proyek Java dari A sampai Z"

Dipublikasikan di grup Random-ID
Halo semuanya, teman-teman terkasih. Hari ini kita akan mengimplementasikan sebuah template (template adalah sebuah pola, dalam konteks kita sama saja) desain Command untuk kebutuhan kita. Dengan menggunakan templat ini, kami akan bekerja dengan nyaman dan benar dalam memproses perintah bot kami. "Proyek Java dari A hingga Z": Menerapkan Pola Perintah untuk bekerja dengan bot.  Bagian 1 - 1
Teman-teman, apakah Anda menyukai proyek Javarush Telegram Bot ? Jangan malas: beri bintang . Dengan cara ini akan terlihat jelas bahwa dia menarik, dan akan lebih menyenangkan untuk mengembangkannya!
Untuk memulainya, ada baiknya untuk membicarakan tentang pola seperti apa ini - Perintah. Tetapi jika saya melakukan ini, artikelnya akan menjadi sangat besar dan rumit. Oleh karena itu, saya memilih materi untuk belajar mandiri:
  1. Ini adalah artikel saya dari 4 tahun yang lalu. Aku menulisnya saat aku masih junior, jadi jangan menilainya terlalu keras.
  2. Video orang Swedia yang sangat emosional dan interaktif di YouTube. Saya sangat merekomendasikannya. Dia berbicara dengan indah, bahasa Inggrisnya jelas dan dapat dimengerti. Dan secara umum, dia memiliki video tentang pola desain lainnya.
  3. Di komentar artikel saya, seseorang Nullptr35 merekomendasikan video ini .
Ini seharusnya cukup untuk membenamkan diri Anda dalam topik tersebut dan memiliki pemikiran yang sama dengan saya. Nah, mereka yang familiar dengan pola desain ini bisa melewatinya dengan aman dan melanjutkan.

Kami menulis JRTB-3

Semuanya sama seperti sebelumnya:
  1. Kami memperbarui cabang utama.
  2. Berdasarkan cabang utama yang diperbarui, kami membuat JRTB-3 baru .
  3. Mari terapkan polanya.
  4. Kami membuat komitmen baru yang menjelaskan pekerjaan yang telah dilakukan.
  5. Kami membuat permintaan tarik, memeriksanya, dan jika semuanya baik-baik saja, kami menggabungkan pekerjaan kami.
Saya tidak akan menunjukkan poin 1-2: Saya telah menjelaskannya dengan sangat hati-hati di artikel sebelumnya, jadi mari kita langsung menerapkan template. Mengapa template ini cocok untuk kita? Ya, karena setiap kali kita menjalankan perintah, kita akan menuju ke metode onUpdateReceived(Update update) , dan bergantung pada perintahnya, kita akan menjalankan logika yang berbeda. Tanpa pola ini, kita akan mempunyai banyak pernyataan if-else if. Sesuatu seperti ini:
if (message.startsWith("/start")) {
   doStartCommand();
} else if(message.startsWith("/stop")) {
   doStopCommand();
} else if(message.startsWith("/addUser")) {
   doAddUserCommand();
}
...
else if(message.startsWith("/makeMeHappy")) {
   doMakeMeHappyCommand();
}
Terlebih lagi, jika ada elipsis, mungkin ada beberapa lusin tim lagi. Dan bagaimana cara menanganinya secara normal? Bagaimana cara mendukungnya? Sulit dan sulit. Artinya opsi ini tidak cocok untuk kita. Seharusnya terlihat seperti ini:
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);
}
Itu saja! Dan tidak peduli berapa banyak perintah yang kita tambahkan, bagian kode ini tidak akan berubah. Apa yang dia lakukan? If pertama memastikan bahwa pesan dimulai dengan awalan perintah "/". Jika demikian, maka kita pilih baris hingga spasi pertama dan cari perintah yang sesuai di CommandContainer; segera setelah kita menemukannya, kita jalankan perintahnya. Dan itu saja...) Jika Anda memiliki keinginan dan waktu, Anda dapat menerapkan bekerja dengan tim, pertama dalam satu kelas sekaligus, dengan banyak kondisi dan sebagainya, dan kemudian menggunakan template. Anda akan melihat perbedaannya. Betapa indahnya hal itu! Pertama, mari kita buat paket di sebelah paket bot, yang akan diberi nama command . "Proyek Java dari A hingga Z": Menerapkan Pola Perintah untuk bekerja dengan bot.  Bagian 1 - 2Dan sudah di dalam paket ini akan ada semua kelas yang berhubungan dengan implementasi perintah. Kita memerlukan satu antarmuka untuk bekerja dengan perintah. Untuk kasus ini, mari kita buat:
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);
}
Pada titik ini, kita tidak perlu menerapkan operasi kebalikan dari perintah, jadi kita akan melewatkan metode ini (tidak dijalankan). Dalam metode eksekusi, objek Update muncul sebagai argumen - persis seperti argumen yang ada pada metode utama kita di bot. Objek ini akan berisi semua yang diperlukan untuk memproses perintah. Selanjutnya, kita akan menambahkan enum yang akan menyimpan nilai perintah (mulai, berhenti, dan seterusnya). Kenapa kita perlu ini? Sehingga kita hanya mempunyai satu sumber kebenaran untuk nama tim. Kami juga membuatnya di paket perintah kami . Sebut saja 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;
   }

}
Kami juga membutuhkan layanan yang akan mengirimkan pesan melalui bot. Untuk melakukan ini, kami akan membuat paket layanan di sebelah paket perintah , yang akan kami tambahkan semua layanan yang diperlukan. Di sini perlu difokuskan pada apa yang saya maksud dengan kata layanan dalam kasus ini. Jika kita mempertimbangkan sebuah aplikasi, maka sering kali dibagi menjadi beberapa lapisan: lapisan untuk bekerja dengan titik akhir - pengontrol, lapisan logika bisnis - layanan, dan lapisan untuk bekerja dengan database - repositori. Oleh karena itu, dalam kasus kami, layanan adalah kelas yang mengimplementasikan beberapa jenis logika bisnis. Bagaimana cara membuat layanan dengan benar? Pertama, buat antarmuka untuk itu dan implementasinya. Tambahkan implementasi menggunakan anotasi `@Service` ke Konteks Aplikasi aplikasi SpringBoot kita, dan, jika perlu, kencangkan menggunakan anotasi `@Autowired`. Oleh karena itu, kami membuat antarmuka SendBotMessageService (dalam penamaan layanan biasanya mereka menambahkan Layanan di akhir nama):
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);
}
Selanjutnya kita buat implementasinya:
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();
       }
   }
}
Seperti inilah implementasinya. Keajaiban yang paling penting adalah tempat sang desainer diciptakan. Menggunakan anotasi @Autowired pada konstruktor, SpringBoot akan mencari objek kelas ini dalam Konteks Aplikasinya. Dan dia sudah ada di sana. Cara kerjanya seperti ini: di aplikasi kita, di mana pun kita dapat mengakses bot dan melakukan sesuatu. Dan layanan ini bertanggung jawab untuk mengirim pesan. Agar kami tidak menulis hal seperti ini setiap saat di setiap tempat:
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();
}
Kami telah memindahkan logika ini ke kelas terpisah dan akan menggunakannya jika perlu. Sekarang kita perlu menerapkan tiga perintah: StartCommand, StopCommand dan UnknownCommand. Kami membutuhkannya sehingga kami memiliki sesuatu untuk mengisi wadah perintah kami. Untuk saat ini, teks-teks tersebut masih kering dan tidak informatif; untuk keperluan tugas ini, hal ini tidak terlalu penting. Jadi, Perintah Mulai:
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);
   }
}
Silakan baca komentar dengan seksama sebelum desainer. Ketergantungan melingkar ( circular dependency ) dapat terjadi akibat arsitektur yang kurang tepat. Dalam kasus kami, kami akan memastikan semuanya berfungsi dan benar. Objek sebenarnya dari Konteks Aplikasi akan ditambahkan saat membuat perintah yang sudah ada di CommandContainer. Perintah Berhenti:
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);
   }
}
Dan Perintah Tidak Diketahui. Mengapa kita membutuhkannya? Bagi kami, ini adalah perintah penting yang akan merespons jika kami tidak dapat menemukan perintah yang diberikan kepada kami. Kita juga membutuhkan NoCommand dan HelpCommand.
  • NoCommand - akan bertanggung jawab atas situasi ketika pesan tidak dimulai dengan perintah sama sekali;
  • HelpCommand akan menjadi panduan bagi pengguna, semacam dokumentasi.
Mari tambahkan 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);
   }
}
Tanpa Perintah:
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);
   }
}
Dan untuk tugas ini masih ada 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);
   }
}
Selanjutnya, mari tambahkan wadah untuk perintah kita. Ini akan menyimpan objek perintah kami dan berdasarkan permintaan kami berharap untuk menerima perintah yang diperlukan. Sebut saja 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);
   }

}
Seperti yang Anda lihat, semuanya dilakukan dengan sederhana. Kami memiliki peta yang tidak dapat diubah dengan kunci berupa nilai perintah dan nilai dalam bentuk objek perintah bertipe Command. Di konstruktor, kami mengisi peta yang tidak dapat diubah satu kali dan mengaksesnya selama operasi aplikasi. Metode utama dan satu-satunya untuk bekerja dengan container adalah retrieveCommand(String commandIdentifier) ​​​​. Ada perintah bernama UnknownCommand, yang bertanggung jawab jika kita tidak dapat menemukan perintah yang sesuai. Sekarang kita siap untuk mengimplementasikan container ke dalam kelas bot kita - di JavaRushTelegramBot: Inilah tampilan kelas bot kita sekarang:
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;
   }
}
Dan itu saja, perubahan kode telah selesai. Bagaimana cara memeriksanya? Anda perlu meluncurkan bot dan memeriksa apakah semuanya berfungsi. Untuk melakukan ini, saya memperbarui token di application.properties, mengatur yang benar, dan meluncurkan aplikasi di kelas JavarushTelegramBotApplication: "Proyek Java dari A hingga Z": Menerapkan Pola Perintah untuk bekerja dengan bot.  Bagian 1 - 3Sekarang kita perlu memeriksa apakah perintah berfungsi seperti yang diharapkan. Saya memeriksanya langkah demi langkah:
  • Perintah Hentikan;
  • MulaiPerintah;
  • Perintah Bantuan;
  • Tanpa Perintah;
  • Perintah Tidak Diketahui.
Inilah yang terjadi: "Proyek Java dari A hingga Z": Menerapkan Pola Perintah untuk bekerja dengan bot.  Bagian 1 - 4Bot bekerja persis seperti yang kami harapkan. Dilanjutkan melalui tautan .

Daftar semua materi dalam seri ini ada di awal artikel ini.

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