文章摘要(AI生成)
行为型模式负责算法和对象间职责的分配,行为模式描述了对象和类的模式,以及他们之间的通信模式行为模式包括责任链,命令模式,解释器模式,迭代器模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,模板模式,访问者模式责任链模式定义将对象练成一条链,并沿着这条链传递请求,知道有一个对象处理它。角色
行为型模式
负责算法和对象间职责的分配,行为模式描述了对象和类的模式,以及他们之间的通信模式
行为模式包括责任链,命令模式,解释器模式,迭代器模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,模板模式,访问者模式
责任链模式
定义
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关 系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
优缺点
优点
责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的,处 理者可以不用知道请求的全貌(例如在J2EE项目开发中,可以剥离出无状态Bean由责任链处 理),两者解耦,提高系统的灵活性。
缺点
责任链有两个非常显著的缺点:一是性能问题,每个请求都是从链头遍历到链尾,特别 是在链比较长的时候,性能是一个非常大的问题。二是调试不很方便,特别是链条比较长, 环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。
注意事项
链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个 最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免 无意识地破坏系统性能
最佳实践
说父类实现了请求传递的功能,子类实现请求 的处理,符合单一职责原则,各个实现类只完成一个动作或逻辑,也就是只有一个原因引起 类的改变
责任链模式屏蔽了请求的处理过程,你发起一个请求到底是谁处理的,这个你不用关 心,只要你把请求抛给责任链的第一个处理者,最终会返回一个处理结果(当然也可以不做 任何处理),作为请求者可以不用知道到底是需要谁来处理的,这是责任链模式的核心,同 时责任链模式也可以作为一种补救模式来使用。
角色
- 处理者(handler):处理程序角色定义了处理请求的接口处理程序角色知道“下一个处理者”是谁,如果自己无法处理请求,它会将请求转给“下一个处理者”。 当然,“下一个处理者”也是Handler角色.
- 具体的处理者(ConcreteHandler): 处理请求的具体角色.
- 请求者(client):客户端角色是向第一个ConcreteHandler角色发送请求的角色。
spring实现
我们用一个链式来处理一批有不同问题的类。
问题类
用来表示发生问题的类,不同问题由问题编号number区分
public class Trouble {
private int number;
public Trouble(int number) {
super();
this.number = number;
}
@Override
public String toString() {
return "Trouble [number=" + number + "]";
}
public int getNumber() {
return number;
}
}
处理者
用来表示解决问题的抽象类
@Slf4j
public abstract class Support {
private String name;
private Support next;
public Support setNext(Support next) {
this.next = next;
return next;
}
protected void setName(String name){
this.name = name;
}
@Override
public String toString() {
return "Support [name=" + name + "]";
}
public final void support(Trouble trouble) {
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
next.support(trouble);
} else {
fail(trouble);
}
}
protected void fail(Trouble trouble) {
// TODO Auto-generated method stub
log.info(trouble + "cannot be resolved. ");
}
protected void done(Trouble trouble) {
// TODO Auto-generated method stub
log.info(trouble + "is resolved by " + this + ".");
}
protected abstract boolean resolve(Trouble trouble);
}
具体处理者
我们按问题编号,规定了问题编号小于指定编号有limitSupport处理,奇数编号有OddSupport处理,指定编号由SpecialSupport处理,NoSupport表示不处理
不处理-NoSupport
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class NoSupport extends Support {
@Override
protected void setName(String name) {
super.setName(name);
}
@Override
protected boolean resolve(Trouble trouble) {
// TODO Auto-generated method stub
return false;
}
}
处理奇数编号的问题-OddSupport
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class OddSupport extends Support {
@Override
protected void setName(String name) {
super.setName(name);
}
@Override
protected boolean resolve(Trouble trouble) {
// TODO Auto-generated method stub
if(trouble.getNumber()%2==1){
return true;
}else{
return false;
}
}
}
处理小于指定编号的问题-LimitSupport
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class LimitSupport extends Support {
private int limit;
public void setLimit(int limit) {
this.limit = limit;
}
@Override
protected void setName(String name) {
super.setName(name);
}
@Override
protected boolean resolve(Trouble trouble) {
// TODO Auto-generated method stub
if(trouble.getNumber()<limit){
return true;
}else{
return false;
}
}
}
处理指定编号的问题-SpecialSupport
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class SpecialSupport extends Support {
private int number;
public void setNumber(int number) {
this.number = number;
}
@Override
protected void setName(String name) {
super.setName(name);
}
@Override
protected boolean resolve(Trouble trouble) {
// TODO Auto-generated method stub
if(trouble.getNumber()==number){
return true;
}else{
return false;
}
}
}
不处理问题-NoSupport
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class NoSupport extends Support {
@Override
protected void setName(String name) {
super.setName(name);
}
@Override
protected boolean resolve(Trouble trouble) {
// TODO Auto-generated method stub
return false;
}
}
处理者生成器
用来生成多个相同类型的处理器
@Component
public abstract class SupportGenerator {
@Lookup
protected abstract NoSupport noSupport();
@Lookup
protected abstract OddSupport oddSupport();
@Lookup
protected abstract SpecialSupport specialSupport();
@Lookup
protected abstract LimitSupport limitSupport();
public NoSupport genNoSupport(String name){
NoSupport noSupport = noSupport();
noSupport.setName(name);
return noSupport;
}
public OddSupport genOddSupport(String name){
OddSupport oddSupport = oddSupport();
oddSupport.setName(name);
return oddSupport;
}
public LimitSupport genLimitSupport(String name, int limit){
LimitSupport limitSupport = limitSupport();
limitSupport.setLimit(limit);
limitSupport.setName(name);
return limitSupport;
}
public SpecialSupport genSpecialSupport(String name, int number){
SpecialSupport specialSupport = specialSupport();
specialSupport.setName(name);
specialSupport.setNumber(number);
return specialSupport;
}
}
测试
@RestController
public class ChainController {
@Resource
SupportGenerator supportGenerator;
@GetMapping("chain")
public String chain(){
Support alice = supportGenerator.genNoSupport("Alice");
Support bob = supportGenerator.genLimitSupport("Bob", 100);
Support charlie = supportGenerator.genSpecialSupport("Charlie", 429);
Support diana = supportGenerator.genLimitSupport("Diana", 200);
Support elmo = supportGenerator.genOddSupport("Elmo");
Support fred = supportGenerator.genLimitSupport("Fred", 300);
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
for (int i = 0; i < 500; i+=33) {
alice.support(new Trouble(i));
}
return "SUCCESS";
}
}
测试结果为:
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=0]is resolved by Support [name=Bob].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=33]is resolved by Support [name=Bob].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=66]is resolved by Support [name=Bob].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=99]is resolved by Support [name=Bob].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=132]is resolved by Support [name=Diana].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=165]is resolved by Support [name=Diana].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=198]is resolved by Support [name=Diana].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=231]is resolved by Support [name=Elmo].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=264]is resolved by Support [name=Fred].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=297]is resolved by Support [name=Elmo].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=330]cannot be resolved.
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=363]is resolved by Support [name=Elmo].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=396]cannot be resolved.
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=429]is resolved by Support [name=Charlie].
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=462]cannot be resolved.
2022-08-27 17:04:07.470 INFO 15824 --- [nio-8080-exec-1] c.e.d.ChainOfResponsibility.Support : Trouble [number=495]is resolved by Support [name=Elmo].
命令模式
定义
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请 求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
优缺点
优点
-
类间解耦
调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command 抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
-
可扩展性
Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严 重的代码耦合。
-
命令模式结合其他模式会更优秀
命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少 Command子类的膨胀问题。
缺点
命令模式也是有缺点的,请看Command的子类:如果有N个命令,问题就出来 了,Command的子类就可不是几个,而是N个,这个类膨胀得非常大,这个就需要读者在项目中慎重考虑使用。
使用场景
只要你认为是命令的地方就可以采用命令模式,例如,在GUI开发中,一个按钮的点击 是一个命令,可以采用命令模式;模拟DOS命令的时候,当然也要采用命令模式;触发-反 馈机制的处理等
最佳实践
我们可以在项目中通过有意义的类名或命令名处理命令角色和接收者角色 的耦合关系(这就是约定),减少高层模块(Client类)对低层模块(Receiver角色类)的依 赖关系,提高系统整体的稳定性。
角色
-
Command(命令):Command 角色负责定义命令的接口(API )。
-
ConcreteCommand(具体的命令):ConcreteCommand角色负责实现在Command 角色中定义的接口(API)
-
Receiver(接收者):Receiver角色是Command角色执行命令时的对象,也可以称其为命令接收者。
-
Client(请求者):Client角色负责生成ConcreteCommand角色并分配 Receiver 角色。
-
Invoker(执行者):Invoker角色是开始执行命令的角色,它会调用在Command 角色中定义的接口API。
spring实现
我们通过对灯发送打开和关闭命令,来控制灯的开关状态
命令
public interface Command {
// 执行
void execute();
// 撤销
void undo();
}
接受者
使用灯作为命令接收者
@Component
@Slf4j
public class LightReceiver {
private boolean status;
public void on() {
status = true;
log.info("电灯打开了。状态为{}", true);
}
public void off() {
status = false;
log.info("电灯关闭了。状态为{}", false);
}
public void undo(){
status = !status;
log.info("上一条指令撤销!状态为{}", status);
}
public boolean isStatus() {
return status;
}
}
具体命令
打开命令
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class LightOnCommand implements Command {
private LightReceiver lightReceiver;
@Override
public void execute() {
lightReceiver.on();
}
@Override
public void undo() {
lightReceiver.undo();
}
public void setLightReceiver(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
}
关闭命令
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class LightOffCommand implements Command{
private LightReceiver lightReceiver;
@Override
public void execute() {
lightReceiver.off();
}
@Override
public void undo() {
lightReceiver.undo();
}
public void setLightReceiver(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
}
命令生成器
@Component
public abstract class CommandGenerator {
@Lookup
protected abstract LightOnCommand lightOnCommand();
@Lookup
protected abstract LightOffCommand lightOffCommand();
public LightOnCommand genLightOnCommand(LightReceiver lightReceiver){
LightOnCommand lightOnCommand = lightOnCommand();
lightOnCommand.setLightReceiver(lightReceiver);
return lightOnCommand;
}
public LightOffCommand genLightOffCommand(LightReceiver lightReceiver){
LightOffCommand lightOffCommand = lightOffCommand();
lightOffCommand.setLightReceiver(lightReceiver);
return lightOffCommand;
}
}
执行者
我们通过一个命令执行器来添加、撤销和清除命令
public class CommandInvoker {
static Stack<Command> commands = new Stack<>();
public static void setCommand(Command command) {
commands.add(command);
}
public static void executeCommands(){
for(Command command: commands){
command.execute();
}
}
public static void clearCommands(){
commands.clear();
}
public static void undoCommand(){
if(!commands.empty()) {
Command command = commands.pop();
command.undo();
}
}
}
测试
@RestController
@Slf4j
public class CommandController {
@Resource
private LightReceiver lightReceiver;
@Resource
private CommandGenerator commandGenerator;
@GetMapping("command")
public boolean command(){
CommandInvoker.setCommand(commandGenerator.genLightOnCommand(lightReceiver));
CommandInvoker.setCommand(commandGenerator.genLightOffCommand(lightReceiver));
CommandInvoker.executeCommands();
CommandInvoker.undoCommand();
CommandInvoker.undoCommand();
return lightReceiver.isStatus();
}
}
测试结果为:
2022-08-27 19:00:29.976 INFO 24472 --- [nio-8080-exec-1] c.e.designpattern.Command.LightReceiver : 电灯打开了。状态为true
2022-08-27 19:00:29.977 INFO 24472 --- [nio-8080-exec-1] c.e.designpattern.Command.LightReceiver : 电灯关闭了。状态为false
2022-08-27 19:00:29.977 INFO 24472 --- [nio-8080-exec-1] c.e.designpattern.Command.LightReceiver : 上一条指令撤销!状态为true
2022-08-27 19:00:29.977 INFO 24472 --- [nio-8080-exec-1] c.e.designpattern.Command.LightReceiver : 上一条指令撤销!状态为false
解释器模式
定义
给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
优缺点
优点
解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。
缺点
-
解释器模式会引起类膨胀
每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
-
解释器模式采用递归调用方法
每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。想想看,如果要排查一个语法错误,我们是不是要一个断点一个断点地调试下去,直到最小的语法单元。
-
效率问题
解释器模式由于使用了大量的循环和递归,效率是一个不容忽视的问题,特别是一用于解析复杂、冗长的语法时,效率是难以忍受的。
使用场景
-
重复发生的问题可以使用解释器模式
例如,多个应用服务器,每天产生大量的日志,需要对日志文件进行分析处理,由于各 个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都 是相同的,但是非终结符表达式就需要制定了。在这种情况下,可以通过程序来一劳永逸地 解决该问题。
-
一个简单语法需要解释的场景
为什么是简单?看看非终结表达式,文法规则越多,复杂度越高,而且类间还要进行递 归调用(看看我们例子中的栈)。想想看,多个类之间的调用你需要什么样的耐心和信心去 排查问题。因此,解释器模式一般用来解析比较标准的字符集,例如SQL语法分析,不过该 部分逐渐被专用工具所取代。
最佳实践
解释器模式在实际的系统开发中使用得非常少,因为它会引起效率、性能以及维护等问 题,一般在大中型的框架型项目能够找到它的身影,如一些数据分析工具、报表设计工具、 科学计算工具等,若你确实遇到“一种特定类型的问题发生的频率足够高”的情况,准备使用 解释器模式时,可以考虑一下Expression4J、MESP(Math Expression String Parser)、Jep等 开源的解析工具包(这三个开源产品都可以通过百度、Google搜索到,请读者自行查询), 功能都异常强大,而且非常容易使用,效率也还不错,实现大多数的数学运算完全没有问 题,自己没有必要从头开始编写解释器。
角色
- AbstractExpression(抽象表达式):AbstractExpression角色定义了语法树节点的共同接口(API)。
- TerminalExpression(终结符表达式):TerminalExpression角色对应BNF中的终结符表达式。
- NonterminalExpression(非终结符表达式):NonterminalExpression角色对应BNF中的非终结符表达式。
- Context(文脉、上下文):Context角色为解释器进行语法解析提供了必要的信息。
- Client(请求者):为了推导语法树,Client角色会调用TerminalExpression 角色和NonterminalExpression 角色
spring 实现
借助解释器,我们可以实现一下迷你语言。迷你语言的用途是控制无线玩具车。虽说是控制无线玩具车,其实能做的事情不过以下3种。
- 前进1米( go )
- 右转(right )
- 左转( left )
以上就是可以向玩具车发送的命令。go是前进1米后停止的命令; right是原地向右转的命令; left是原地向左转的命令。在实际操作时,是不能完全没有偏差地原地转弯的。为了使问题简单化,我们这里并不会改变玩具车的位置,而是像将其放在旋转桌子上一样,让它转个方向。
如果只是这样,大家可能感觉没什么意思。所以,接下来我们再加一个循环命令。
- ·重复( repeat )
抽象表达式
public abstract class Node {
public abstract void parse(Context context) throws ParseException;
}
非终结符表达式
程序开始符解析出多条指令:
@Component
public class ProgramNode extends Node {
private Node node;
@Resource
private NodeGenerator nodeGenerator;
@Override
public void parse(Context context) throws ParseException {
// TODO Auto-generated method stub
context.skipToken("program");
node = nodeGenerator.commandListNode();
node.parse(context);
}
@Override
public String toString() {
return "Program[" + node + "]";
}
}
多条指令解析为单条指令:
@Component
public class CommandListNode extends Node {
private ArrayList<Node> list = new ArrayList<Node>();
private Node node;
@Resource
private NodeGenerator nodeGenerator;
@Override
public void parse(Context context) throws ParseException {
// TODO Auto-generated method stub
while(true){
if(context.currentToken()==null){
throw new ParseException("Missing 'end");
}else if(context.currentToken().equals("end")){
context.skipToken("end");
break;
}else{
node = nodeGenerator.commandNode();
node.parse(context);
list.add(node);
}
}
}
@Override
public String toString() {
return list.toString();
}
}
单条指令判断指令为重复指令还是移动指令:
@Component
public class CommandNode extends Node {
private Node node;
@Resource
private NodeGenerator nodeGenerator;
@Override
public void parse(Context context) throws ParseException {
// TODO Auto-generated method stub
if(context.currentToken().equals("repeat")){
node = nodeGenerator.repeatCommandNode();
node.parse(context);
}else{
node = nodeGenerator.primitiveCommandNode();
node.parse(context);
}
}
@Override
public String toString() {
return "[" + node + "]";
}
}
重复指令解析:
@Component
public class RepeatCommandNode extends Node {
private int number;
private Node node;
@Resource
private NodeGenerator nodeGenerator;
@Override
public void parse(Context context) throws ParseException {
// TODO Auto-generated method stub
context.skipToken("repeat");
number = context.currentNumber();
context.nextToken();
node = nodeGenerator.commandListNode();
node.parse(context);
}
@Override
public String toString() {
return "[repeat" + number + " " + node + "]";
}
}
单条指令解析:
@Component
public class CommandListNode extends Node {
private ArrayList<Node> list = new ArrayList<Node>();
private Node node;
@Resource
private NodeGenerator nodeGenerator;
@Override
public void parse(Context context) throws ParseException {
// TODO Auto-generated method stub
while(true){
if(context.currentToken()==null){
throw new ParseException("Missing 'end");
}else if(context.currentToken().equals("end")){
context.skipToken("end");
break;
}else{
node = nodeGenerator.commandNode();
node.parse(context);
list.add(node);
}
}
}
@Override
public String toString() {
return list.toString();
}
}
终结符表达式
移动指令(前进、左转、右转)解析:
@Component
public class PrimitiveCommandNode extends Node {
private String name;
@Override
public void parse(Context context) throws ParseException {
// TODO Auto-generated method stub
name = context.currentToken();
context.skipToken(name);
if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
throw new ParseException(name + "is undefined");
}
}
@Override
public String toString() {
return name;
}
}
上下文
提供语法解析上下文的类:
@Component
public class Context {
private StringTokenizer tokenizer;
private String currentToken;
public void initContext(String text){
tokenizer = new StringTokenizer(text);
nextToken();
}
public String nextToken() {
// TODO Auto-generated method stub
if (tokenizer.hasMoreTokens()) {
currentToken = tokenizer.nextToken();
} else {
currentToken = null;
}
return currentToken;
}
public String currentToken() {
return currentToken;
}
public void skipToken(String token) throws ParseException {
if (!token.equals(currentToken)) {
throw new ParseException("Warning: " + token + "is excepted, but " + currentToken + "is found. ");
}
nextToken();
}
public int currentNumber() throws ParseException {
int number = 0;
try {
number = Integer.parseInt(currentToken);
} catch (NumberFormatException e) {
throw new ParseException("Warning" + e);
}
return number;
}
}
测试
@RestController
@Slf4j
public class InterpreterController {
@Resource
private NodeGenerator nodeGenerator;
@GetMapping("interceptor")
public String interceptor() throws IOException, ParseException {
File file = ResourceUtils.getFile("classpath:templates/Program.txt");
BufferedReader reader = new BufferedReader(new FileReader(file));
String text;
while ((text = reader.readLine()) != null) {
log.info("text=\"" + text + "\"");
Node node = nodeGenerator.programNode();
node.parse(nodeGenerator.genContext(text));
log.info("node = " + node);
}
reader.close();
return "success";
}
}
测试结果:
2022-08-27 20:08:18.853 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : text="program end"
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : node = Program[[]]
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : text="program go end"
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : node = Program[[[go]]]
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : text="program go right go right go right end"
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : node = Program[[[right], [right], [right], [right], [right], [right], [right]]]
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : text="program repeat 4 go right end end"
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : node = Program[[[right], [right], [right], [right], [right], [right], [right], [right], [right], [right]]]
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : text="program repeat 4 repeat 3 go right go left end right end end "
2022-08-27 20:08:18.854 INFO 9864 --- [nio-8080-exec-2] c.e.d.Interpreter.InterpreterController : node = Program[[[right], [right], [right], [right], [right], [right], [right], [right], [right], [right], [right], [right], [right], [right], [right], [right], [right]]]
评论区