如何选择合适数据类型存储多实现类型的Java命令类?是否有更优实现方案?
嘿,针对你的命令类存储需求,我来给你梳理下可行的方案:
一、有没有符合场景的数据类型?
当然有!最直接的选择就是用**commandBase**作为容器的泛型类型——毕竟你所有的命令类都继承自它,完全能容纳testCommand和testCommand2的实例,而且后续也能通过类型转换调用对应接口的方法。
示例代码如下:
// ArrayList存储 ArrayList<commandBase> registeredCommands = new ArrayList<>(); registeredCommands.add(new testCommand()); registeredCommands.add(new testCommand2()); // 单个变量存储 commandBase currentCommand = new testCommand(); currentCommand = new testCommand2();
如果你想让语义更清晰,也可以定义一个空的标记接口(比如public interface Command {}),让commandBase实现它,然后用Command作为容器类型——本质和用commandBase没啥区别,但代码可读性会更好一点。
二、更优的实现方式
虽然强制转型能解决问题,但不仅容易抛出ClassCastException,代码也显得不够优雅。这里有几个更靠谱的方案:
1. 访问者模式:彻底避免强制转型
通过访问者模式,你可以安全地处理不同类型的命令,完全不用手动转型。具体实现步骤如下:
首先定义访问者接口,让每个命令类实现“接受访问”的方法:
// 访问者接口,对应两种命令类型 public interface CommandVisitor { void visitTypeA(commandTypeA command); void visitTypeB(commandTypeB command); } // 修改commandBase,添加抽象的accept方法 public abstract class commandBase { public abstract String name(); public abstract void accept(CommandVisitor visitor); } // 让testCommand实现accept方法,把自己交给对应访问逻辑 public class testCommand extends commandBase implements commandTypeA { @Override public type executable() { return type.EVERYONE; } @Override public String name() { return "test"; } @Override public void accept(CommandVisitor visitor) { visitor.visitTypeA(this); } } // testCommand2同理 public class testCommand2 extends commandBase implements commandTypeB { @Override public String description() { return "A testCommand"; } @Override public String name() { return "test"; } @Override public void accept(CommandVisitor visitor) { visitor.visitTypeB(this); } }
使用的时候,只需要实现访问者逻辑,遍历命令列表即可:
CommandVisitor commandHandler = new CommandVisitor() { @Override public void visitTypeA(commandTypeA command) { System.out.println(command.executable()); } @Override public void visitTypeB(commandTypeB command) { System.out.println(command.description()); } }; for(commandBase cmd : registeredCommands) { System.out.println(cmd.name()); cmd.accept(commandHandler); }
这种方式类型安全,扩展性强,后续新增命令类型只需要在访问者接口里加对应的方法就行。
2. Java 17+密封类+模式匹配:简洁又安全
如果你的项目用的是Java 17或更高版本,密封类+模式匹配的组合会非常省心。首先把commandBase定义为密封类,限定它的子类范围:
// 密封类,只允许指定的类继承它 public sealed abstract class commandBase permits testCommand, testCommand2 { public abstract String name(); }
然后使用的时候,直接用模式匹配的instanceof就能安全调用对应接口的方法:
for(commandBase cmd : registeredCommands) { System.out.println(cmd.name()); // 自动类型转换,不需要手动强转 if(cmd instanceof commandTypeA typeACmd) { System.out.println(typeACmd.executable()); } else if(cmd instanceof commandTypeB typeBCmd) { System.out.println(typeBCmd.description()); } }
这种方式代码简洁,完全没有强制转型的烦恼,适合新版本Java项目。
3. 统一接口(业务允许的情况下)
如果你的业务逻辑允许默认值,可以把两个接口的方法合并到一个统一接口里,用默认方法实现空值或默认行为:
public interface UnifiedCommand { String name(); // 默认实现,不需要的命令类可以不用重写 default type executable() { return type.DEFAULT; // 或者返回null,根据你的枚举调整 } default String description() { return "No description"; } }
然后让commandBase实现这个接口,testCommand和testCommand2只需要重写自己需要的方法就行。不过这种方式只适合业务上能接受默认值的场景,否则可能会有逻辑隐患。
内容的提问来源于stack exchange,提问作者alexanderbg

