สวัสดีทุกคนเพื่อนรัก วันนี้เราจะใช้เทมเพลต (เทมเพลตคือรูปแบบ ในบริบทของเรา มันเป็นสิ่งเดียวกัน) ของการออกแบบ Command ตามความต้องการของเรา การใช้เทมเพลตนี้จะทำให้เราทำงานกับการประมวลผลคำสั่งของบอทได้อย่างสะดวกและถูกต้อง
อันดับแรก เป็นการดีที่จะพูดถึงรูปแบบนี้ - Command แต่ถ้าผมทำเช่นนี้ บทความจะใหญ่และยุ่งยากมาก ดังนั้นฉันจึงเลือกสื่อการเรียนด้วยตนเอง:
และในแพ็คเกจนี้จะมีคลาสทั้งหมดที่เกี่ยวข้องกับการนำคำสั่งไปใช้แล้ว เราต้องการอินเทอร์เฟซเดียวสำหรับการทำงานกับคำสั่ง สำหรับกรณีนี้ มาสร้างมันขึ้นมา:
ตอนนี้เราต้องตรวจสอบว่าคำสั่งทำงานตามที่คาดไว้หรือไม่ ฉันตรวจสอบทีละขั้นตอน:
บอททำงานได้ตรงตามที่เราคาดไว้ ติดตามต่อได้ตามลิงค์ครับ
เพื่อนๆ คุณชอบโครงการ Javarush Telegram Bot ไหม ? อย่าขี้เกียจ: ให้ดาวเลย ด้วยวิธีนี้จะชัดเจนว่าเขาน่าสนใจและการพัฒนาเขาจะดีกว่า! |
- นี่คือบทความของฉันเมื่อ 4 ปีที่แล้ว ฉันเขียนมันตอนที่ฉันยังเป็นรุ่นน้อง ดังนั้นอย่าตัดสินมันรุนแรงเกินไป
- วิดีโอของชาวสวีเดนที่เข้าถึงอารมณ์และโต้ตอบได้บน YouTube ฉันขอแนะนำอย่างยิ่ง เขาพูดได้ไพเราะ ภาษาอังกฤษของเขาชัดเจนและเข้าใจได้ และโดยทั่วไป เขามีวิดีโอเกี่ยวกับรูปแบบการออกแบบอื่นๆ
- ในความคิดเห็นต่อบทความของฉัน มีคนNullptr35แนะนำวิดีโอนี้
เราเขียน JRTB-3
ทุกอย่างเหมือนเดิม:- เราอัพเดตสาขาหลัก
- ตามสาขาหลักที่อัปเดต เราสร้างJRTB -3 ใหม่
- ลองใช้รูปแบบกัน
- เราสร้างคอมมิตใหม่เพื่ออธิบายงานที่ทำเสร็จแล้ว
- เราสร้างคำขอดึง ตรวจสอบ และหากทุกอย่างเรียบร้อย เราจะรวมงานของเราเข้าด้วยกัน
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 
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);
}
ณ จุดนี้ เราไม่จำเป็นต้องใช้การดำเนินการย้อนกลับของคำสั่ง ดังนั้นเราจะข้ามวิธีนี้ (ไม่ดำเนินการ) ในวิธีการดำเนินการ อ็อบเจ็กต์ Updateมาเป็นอาร์กิวเมนต์- เป็นอาร์กิวเมนต์ที่มาพร้อมกับวิธีการหลักของเราในบอท ออบเจ็กต์นี้จะมีทุกสิ่งที่จำเป็นในการประมวลผลคำสั่ง ต่อไป เราจะเพิ่ม enum ที่จะเก็บค่าคำสั่ง (start, stop และอื่นๆ) ทำไมเราถึงต้องการสิ่งนี้? เพื่อให้เรามีแหล่งความจริงเพียงแหล่งเดียวสำหรับชื่อทีม เรายังสร้างมันขึ้นมาใน แพ็คเกจ คำสั่ง ของเรา ด้วย ลองเรียกมันว่า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;
}
}
เรายังต้องการบริการที่จะส่งข้อความผ่านบอทด้วย ในการดำเนินการนี้ เราจะสร้าง แพ็คเกจบริการถัดจากคำสั่ง package ซึ่งเราจะเพิ่มบริการที่จำเป็นทั้งหมด ที่นี่คุ้มค่าที่จะมุ่งเน้นไปที่สิ่งที่ฉันหมายถึงโดยบริการคำในกรณีนี้ หากเราพิจารณาแอปพลิเคชัน มักจะแบ่งออกเป็นหลายเลเยอร์: เลเยอร์สำหรับการทำงานกับอุปกรณ์ปลายทาง - ตัวควบคุม, เลเยอร์ของตรรกะทางธุรกิจ - บริการ และเลเยอร์สำหรับการทำงานกับฐานข้อมูล - ที่เก็บข้อมูล ดังนั้นในกรณีของเรา บริการคือคลาสที่ใช้ตรรกะทางธุรกิจบางประเภท จะสร้างบริการอย่างถูกต้องได้อย่างไร? ขั้นแรก สร้างอินเทอร์เฟซสำหรับอินเทอร์เฟซและการใช้งาน เพิ่มการใช้งานโดยใช้คำอธิบายประกอบ `@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 แล้ว คำสั่งหยุด:
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 ใน Constructor เรากรอกแผนที่ที่ไม่เปลี่ยนรูปหนึ่งครั้งและเข้าถึงได้ตลอดการทำงานของแอปพลิเคชัน วิธีการหลักและวิธีเดียวสำหรับการทำงานกับคอนเทนเนอร์คือgetrecommand(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: 
- หยุดคำสั่ง;
- เริ่มคำสั่ง;
- คำสั่งช่วยเหลือ;
- ไม่มีคำสั่ง;
- คำสั่งที่ไม่รู้จัก

GO TO FULL VERSION