Java实现Logo语言时如何优雅完成多指令的分支切换
基于函数式组件的指令分发实现方案
抛弃if/else和switch硬编码分支,用指令注册表+流式匹配链实现即可,完全覆盖所有Logo指令场景,且全程用Stream、Predicate、Function等JDK原生函数式组件,无额外复杂依赖,扩展成本极低。
核心抽象定义
先定义运行时上下文和函数式约定,所有组件遵循单一职责:
import java.awt.*; import java.util.List; import lombok.Data; // 运行时上下文,存储画笔、画布、坐标等全量状态 @Data public class LogoContext { private int x, y; private int angle; private boolean penDown = true; private Color penColor = Color.BLACK; private Color fillColor = Color.WHITE; private Color screenColor = Color.WHITE; private int penSize = 1; private final List<Shape> renderedShapes = new ArrayList<>(); } // 指令执行器:接收解析后的参数、上下文,执行对应操作 @FunctionalInterface public interface LogoCommandHandler { void execute(List<Object> parsedArgs, LogoContext context); } // 指令匹配规则:判断当前字符串片段是否匹配目标指令 @FunctionalInterface public interface CommandMatcher extends Predicate<String> {} // 参数解析器:从输入串当前位置解析参数,返回解析后的游标位置和参数列表 @FunctionalInterface public interface ArgsParser extends Function<String, ParseResult> {} // 解析结果封装 record ParseResult(int nextCursor, List<Object> args){} // 可执行指令单元 @FunctionalInterface public interface LogoExecutable { void run(LogoContext context); }
指令注册表实现
所有指令的匹配规则、参数解析逻辑、执行逻辑都提前注册到统一注册表,初始化时一次性加载,运行时不需要做分支判断:
import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.IntStream; public class LogoCommandRegistry { private final List<CommandEntry> commandEntries = new ArrayList<>(); public LogoCommandRegistry() { // 注册无参数指令 registerNoArg("CLEARSCREEN", (args, ctx) -> { ctx.getRenderedShapes().clear(); ctx.setX(0); ctx.setY(0); ctx.setAngle(0); }); registerNoArg("HOME", (args, ctx) -> { ctx.setX(0); ctx.setY(0); ctx.setAngle(0); }); registerNoArg("PENUP", (args, ctx) -> ctx.setPenDown(false)); registerNoArg("PENDOWN", (args, ctx) -> ctx.setPenDown(true)); // 注册单int参数指令 registerSingleInt("FORWARD", (args, ctx) -> { int step = (int) args.get(0); // 按当前角度计算新坐标,penDown为真时追加线段到渲染列表 }); registerSingleInt("BACKWARD", (args, ctx) -> { int step = (int) args.get(0); // 反向计算坐标、绘制逻辑 }); registerSingleInt("LEFT", (args, ctx) -> { int delta = (int) args.get(0); ctx.setAngle((ctx.getAngle() - delta + 360) % 360); }); registerSingleInt("RIGHT", (args, ctx) -> { int delta = (int) args.get(0); ctx.setAngle((ctx.getAngle() + delta) % 360); }); registerSingleInt("SETPENSIZE", (args, ctx) -> ctx.setPenSize((int) args.get(0))); // 注册三int(RGB)参数指令 registerTripleInt("SETPENCOLOR", (args, ctx) -> ctx.setPenColor(new Color((int)args.get(0), (int)args.get(1), (int)args.get(2))) ); registerTripleInt("SETFILLCOLOR", (args, ctx) -> ctx.setFillColor(new Color((int)args.get(0), (int)args.get(1), (int)args.get(2))) ); registerTripleInt("SETSCREENCOLOR", (args, ctx) -> ctx.setScreenColor(new Color((int)args.get(0), (int)args.get(1), (int)args.get(2))) ); // 注册RIPETI重复指令,天然支持嵌套 commandEntries.add(new CommandEntry( s -> s.trim().startsWith("RIPETI"), s -> { Matcher matcher = Pattern.compile("RIPETI\\s+(\\d+)\\s*\\[([\\s\\S]*)]").matcher(s.trim()); matcher.find(); int repeatTimes = Integer.parseInt(matcher.group(1)); String innerCmdStr = matcher.group(2); // 递归解析内部指令块 List<LogoExecutable> innerCmds = new LogoExecutor(this).parse(innerCmdStr); return new ParseResult(s.length(), List.of(repeatTimes, innerCmds)); }, (args, ctx) -> { int times = (int) args.get(0); List<LogoExecutable> innerCmds = (List<LogoExecutable>) args.get(1); IntStream.range(0, times).forEach(i -> innerCmds.forEach(cmd -> cmd.run(ctx))); } )); } // 无参指令注册通用方法 private void registerNoArg(String cmdName, LogoCommandHandler handler) { commandEntries.add(new CommandEntry( s -> s.trim().startsWith(cmdName), s -> new ParseResult(s.trim().indexOf(cmdName) + cmdName.length(), Collections.emptyList()), handler )); } // 单int参数指令注册通用方法 private void registerSingleInt(String cmdName, LogoCommandHandler handler) { commandEntries.add(new CommandEntry( s -> s.trim().startsWith(cmdName), s -> { Matcher matcher = Pattern.compile(cmdName + "\\s+(-?\\d+)").matcher(s.trim()); matcher.find(); int arg = Integer.parseInt(matcher.group(1)); return new ParseResult(matcher.end(), List.of(arg)); }, handler )); } // 三int参数指令注册通用方法 private void registerTripleInt(String cmdName, LogoCommandHandler handler) { commandEntries.add(new CommandEntry( s -> s.trim().startsWith(cmdName), s -> { Matcher matcher = Pattern.compile(cmdName + "\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)").matcher(s.trim()); matcher.find(); int r = Integer.parseInt(matcher.group(1)); int g = Integer.parseInt(matcher.group(2)); int b = Integer.parseInt(matcher.group(3)); return new ParseResult(matcher.end(), List.of(r, g, b)); }, handler )); } public List<CommandEntry> getCommandEntries() { return commandEntries; } record CommandEntry(CommandMatcher matcher, ArgsParser parser, LogoCommandHandler handler){} }
流式解析执行逻辑
解析过程全程用Stream流匹配对应指令,完全替代硬编码分支:
import java.util.ArrayList; import java.util.List; public class LogoExecutor { private final LogoCommandRegistry registry; public LogoExecutor(LogoCommandRegistry registry) { this.registry = registry; } public List<LogoExecutable> parse(String input) { List<LogoExecutable> executables = new ArrayList<>(); String remaining = input.trim(); while (!remaining.isEmpty()) { String currentSegment = remaining; // 流式查找匹配的指令,无任何if/switch分支 LogoCommandRegistry.CommandEntry matched = registry.getCommandEntries().stream() .filter(entry -> entry.matcher().test(currentSegment)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("无法识别的指令: " + currentSegment)); ParseResult parseResult = matched.parser().apply(remaining); executables.add(ctx -> matched.handler().execute(parseResult.args(), ctx)); remaining = remaining.substring(parseResult.nextCursor()).trim(); } return executables; } public void execute(String input, LogoContext context) { parse(input).forEach(cmd -> cmd.run(context)); } }
方案特性
- 完全满足函数式编程要求:核心逻辑全部基于
Predicate、Function、Stream、IntStream实现,没有冗余的分支判断 - 符合开闭原则:新增指令只需要在注册表中添加对应注册项,不需要修改核心解析分发逻辑
- 嵌套支持天然实现:RIPETI指令通过递归解析即可支持多层嵌套,不需要额外编写复杂的栈式状态机
- 测试成本低:每个指令的匹配、解析、执行逻辑都是独立的函数单元,可单独做单元测试
内容的提问来源于stack exchange,提问作者anthony eleuteri
相关产品推荐
相关产品推荐

