如何动态处理聊天程序中的命令?
How to dynamically handle commands in a chat program?
我的问题更多的是设计问题。我目前正在 Java 中开发一个 classic 服务器-客户端聊天程序。一切都很好,直到我得到命令。我认为用户发送命令会很方便,然后服务器会处理这些命令以更改他们的昵称。问题是我想编写灵活的代码,最重要的是,面向对象的代码。为了避免无休止的 if/else if 语句来知道键入了什么命令,我认为最好为每个继承自 superclass 命令的命令创建一个 class。然后我可以 return 通过在所有子 class 中覆盖的 getCommand() 函数来执行特定命令。但它根本没有解决我的问题。服务器仍然需要使用 instanceof 测试已 returned 的命令。动态执行此操作的一种方法是从 superclass 命令自动向下转型,然后在服务器 class 中调用适当的函数。例如:
public void processCommand(CommandNick c) {}
public void processCommand(CommandKick c) {}
但我还没有找到任何合适的方法来做到这一点,即使我找到了,我觉得这里仍然存在设计问题。而且我相信有一种很好且灵活的方法可以做到这一点,但几天时间不足以让我弄清楚。有任何想法吗?提前致谢! :)
我假设您的服务器接收的消息是一个带有发件人和字符串的对象。创建您的命令 classes,并在服务器初始化代码中创建一个 HashMap<String, AbstractCommand>
,其中 String
作为键,您的 AbstractCommand
class 作为值。你的命令应该扩展这个 class。像这样注册所有命令:
commandRegistry.put("help", new HelpCommandHandler());
我假设命令是一条消息,前面有 !
。所以当你收到消息时,检查它是否是命令:
Message message = (Your Message)
String messageBody = message.getBody();
Sender messageSender = message.getSender();
if(messageBody.startsWith("!")) {
// Split the message after every space
String[] commandParts = messageBody.split(" ");
// The first element is the command base, like: !help
String baseCommand = commandParts[0];
// Remove the first character from the base, turns !help into help
baseCommand = baseCommand.substring(1, baseCommand.length());
// Creates a new array for the arguments. The length is smaller, because we won't copy the command base
String[] args = new String[commandParts.length - 1];
// Copy the elements of the commandParts array from index 1 into args from index 0
if(args.length > 0) {
System.arraycopy(commandParts, 1, args, 0, commandParts.length - 1);
}
// Your parse method
processCommand(sender, baseCommand, args);
}
public void processCommand(Sender sender, String base, String[] args) {
if(commandRegistry.containsKey(base)) {
commandRegistry.get(base).execute(sender, args);
} else {
// Handle unknown command
}
}
public abstract class AbstractCommand {
public abstract void execute(Sender sender, String[] args);
}
实施示例。我假设您的服务器是单例,您可以使用 Server.get()
或任何类似方法获取它的对象。
public class HelpCommandHandler extends AbstractCommand { /* !help */
@Override
public void execute(Sender sender, String[] args) {
sender.sendMessage("You asked for help."); // Your code might not work like this.
}
}
public class ChangeNickCommandHandler extends AbstractCommand { /* !changenick newNick */
@Override
public void execute(Sender sender, String[] args) {
// I assume you have a List with connected players in your Server class
String username = sender.getUsername(); // Your code might not work like this
Server server = Server.get(); // Get Server instance
server.getUsers().get(username).setNickname(args[0]); // Argument 0. Check if it even exists.
}
}
// Server class. If it isn't singleton, you can make it one like this:
public class Server {
private static Server self;
public static Server init(/* Your args you'd use in a constructor */) { self = new Server(); return get(); }
public static Server get() { return self; }
private List<User> users = new List<User>();
private HashMap<String, AbstractCommand> commandRegitry = new HashMap<>();
// Make construcor private, use init() instead.
private Server() {
commandRegistry.put("help", new HelpCommandHandler());
commandRegistry.put("changenick", new ChangeNickCommandHandler());
}
// Getters
public List<User> getUsers() {
return users;
}
public HashMap<String, AbstractCommand> getRegistry() {
return commandRegistry;
}
}
这是一段伪代码,用于说明您的控制器不需要了解命令处理器(不需要 instanceof)。
abstract class CommandProcessor {
/* return boolean if this Command processed the request */
public static boolean processCommand(String command, User user, Properties chatProperties, Chat chat);
}
/* Handle anything */
public class CommandRemainder extends CommandProcessor {
@Override
public static boolean processCommand(String command, User user, Properties chatProperties, Chat chat) {
chat.appendText("[" + user.getName() + "] " + command);
return true;
}
}
/* Handle color changing */
public class CommandColorizer extends CommandProcessor {
protected static List<String> ALLOWED_COLORS = new ArrayList<>(Arrays.asList("red", "blue", "green"));
@Override
public static boolean processCommand(String command, User user, Properties chatProperties, Chat chat) {
if ("fg:".equals(command.trim().substring(0,3)) {
String color = command.trim().substring(3).trim();
if (ALLOWED_COLORS.contains(color)) {
chat.setForeground(color);
}
return true;
}
return false;
}
}
public class ChatController {
protected Chat chat = new Chat();
protected User user = getUser();
protected Properties chatProperties = getChatProperties();
protected List<CommandProcessor> commandProcessors = getCommandProcessors();
{
chat.addChatListener(new ChatListener(){
@Override
public void userChatted(String userChatString) {
for (CommandProcessor processor : commandProcessors) {
if (processor.processCommand(userChatString, user, chatProperties, chat)) {
break;
}
}
}
});
}
List<CommandProcessor> getCommandProcessors() {
List<CommandProcessor> commandProcessors = new ArrayList<>();
commandProcessors.add(new CommandColorizer());
commandProcessors.add(new CommandRemainder()); // needs to be last
return commandProcessors;
}
}
我的问题更多的是设计问题。我目前正在 Java 中开发一个 classic 服务器-客户端聊天程序。一切都很好,直到我得到命令。我认为用户发送命令会很方便,然后服务器会处理这些命令以更改他们的昵称。问题是我想编写灵活的代码,最重要的是,面向对象的代码。为了避免无休止的 if/else if 语句来知道键入了什么命令,我认为最好为每个继承自 superclass 命令的命令创建一个 class。然后我可以 return 通过在所有子 class 中覆盖的 getCommand() 函数来执行特定命令。但它根本没有解决我的问题。服务器仍然需要使用 instanceof 测试已 returned 的命令。动态执行此操作的一种方法是从 superclass 命令自动向下转型,然后在服务器 class 中调用适当的函数。例如:
public void processCommand(CommandNick c) {}
public void processCommand(CommandKick c) {}
但我还没有找到任何合适的方法来做到这一点,即使我找到了,我觉得这里仍然存在设计问题。而且我相信有一种很好且灵活的方法可以做到这一点,但几天时间不足以让我弄清楚。有任何想法吗?提前致谢! :)
我假设您的服务器接收的消息是一个带有发件人和字符串的对象。创建您的命令 classes,并在服务器初始化代码中创建一个 HashMap<String, AbstractCommand>
,其中 String
作为键,您的 AbstractCommand
class 作为值。你的命令应该扩展这个 class。像这样注册所有命令:
commandRegistry.put("help", new HelpCommandHandler());
我假设命令是一条消息,前面有 !
。所以当你收到消息时,检查它是否是命令:
Message message = (Your Message)
String messageBody = message.getBody();
Sender messageSender = message.getSender();
if(messageBody.startsWith("!")) {
// Split the message after every space
String[] commandParts = messageBody.split(" ");
// The first element is the command base, like: !help
String baseCommand = commandParts[0];
// Remove the first character from the base, turns !help into help
baseCommand = baseCommand.substring(1, baseCommand.length());
// Creates a new array for the arguments. The length is smaller, because we won't copy the command base
String[] args = new String[commandParts.length - 1];
// Copy the elements of the commandParts array from index 1 into args from index 0
if(args.length > 0) {
System.arraycopy(commandParts, 1, args, 0, commandParts.length - 1);
}
// Your parse method
processCommand(sender, baseCommand, args);
}
public void processCommand(Sender sender, String base, String[] args) {
if(commandRegistry.containsKey(base)) {
commandRegistry.get(base).execute(sender, args);
} else {
// Handle unknown command
}
}
public abstract class AbstractCommand {
public abstract void execute(Sender sender, String[] args);
}
实施示例。我假设您的服务器是单例,您可以使用 Server.get()
或任何类似方法获取它的对象。
public class HelpCommandHandler extends AbstractCommand { /* !help */
@Override
public void execute(Sender sender, String[] args) {
sender.sendMessage("You asked for help."); // Your code might not work like this.
}
}
public class ChangeNickCommandHandler extends AbstractCommand { /* !changenick newNick */
@Override
public void execute(Sender sender, String[] args) {
// I assume you have a List with connected players in your Server class
String username = sender.getUsername(); // Your code might not work like this
Server server = Server.get(); // Get Server instance
server.getUsers().get(username).setNickname(args[0]); // Argument 0. Check if it even exists.
}
}
// Server class. If it isn't singleton, you can make it one like this:
public class Server {
private static Server self;
public static Server init(/* Your args you'd use in a constructor */) { self = new Server(); return get(); }
public static Server get() { return self; }
private List<User> users = new List<User>();
private HashMap<String, AbstractCommand> commandRegitry = new HashMap<>();
// Make construcor private, use init() instead.
private Server() {
commandRegistry.put("help", new HelpCommandHandler());
commandRegistry.put("changenick", new ChangeNickCommandHandler());
}
// Getters
public List<User> getUsers() {
return users;
}
public HashMap<String, AbstractCommand> getRegistry() {
return commandRegistry;
}
}
这是一段伪代码,用于说明您的控制器不需要了解命令处理器(不需要 instanceof)。
abstract class CommandProcessor {
/* return boolean if this Command processed the request */
public static boolean processCommand(String command, User user, Properties chatProperties, Chat chat);
}
/* Handle anything */
public class CommandRemainder extends CommandProcessor {
@Override
public static boolean processCommand(String command, User user, Properties chatProperties, Chat chat) {
chat.appendText("[" + user.getName() + "] " + command);
return true;
}
}
/* Handle color changing */
public class CommandColorizer extends CommandProcessor {
protected static List<String> ALLOWED_COLORS = new ArrayList<>(Arrays.asList("red", "blue", "green"));
@Override
public static boolean processCommand(String command, User user, Properties chatProperties, Chat chat) {
if ("fg:".equals(command.trim().substring(0,3)) {
String color = command.trim().substring(3).trim();
if (ALLOWED_COLORS.contains(color)) {
chat.setForeground(color);
}
return true;
}
return false;
}
}
public class ChatController {
protected Chat chat = new Chat();
protected User user = getUser();
protected Properties chatProperties = getChatProperties();
protected List<CommandProcessor> commandProcessors = getCommandProcessors();
{
chat.addChatListener(new ChatListener(){
@Override
public void userChatted(String userChatString) {
for (CommandProcessor processor : commandProcessors) {
if (processor.processCommand(userChatString, user, chatProperties, chat)) {
break;
}
}
}
});
}
List<CommandProcessor> getCommandProcessors() {
List<CommandProcessor> commandProcessors = new ArrayList<>();
commandProcessors.add(new CommandColorizer());
commandProcessors.add(new CommandRemainder()); // needs to be last
return commandProcessors;
}
}