行为篇-解释器模式
Posted zhixuChen200
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了行为篇-解释器模式相关的知识,希望对你有一定的参考价值。
文章目录
前言
解释有拆解、释义的意思,一般可以理解为针对某段文字,按照其语言的特定语法进行解析,再以另一种表达形式表达出来,以达到人们能够理解的目的。类似地,解释器模式(Interpreter)会针对某种语言并基于其语法特征创建一系列的表达式类(包括终极表达式与非终极表达式),利用树结构模式将表达式对象组装起来,最终将其翻译成计算机能够识别并执行的语义树。例如结构型数据库对查询语言SQL的解析,浏览器对html语言的解析,以及操作系统Shell对命令的解析。不同的语言有着不同的语法和翻译方式,这都依靠解释器完成。
提示:以下是本篇文章正文内容,下面案例可供参考
一、语言与表达式
要进行解释翻译工作,必须先研究语法。以人类的语言为例,假如我们要进行英文翻译工作,首先要将句子理解为“非终极表达式”,对它进行拆分,直到单词为止,此时我们可以将单词理解为“终极表达式”。举个具体的例子,我们对英语句子“I like you.”(非终极表达式)进行拆分,按空格分割为单词“I”“like”“you”(终极表达式),然后将每个单词翻译后,再按顺序合并为“我喜欢你”。虽然我们得到了正确的翻译结果,但这种简单的规则也存在例外,例如对句子“How are you?”按照这个规则翻译出来就是“怎么是你?”,这显然不对了。
与此类似,编程语言也是由各种各样的表达式组合起来的树形结构,也就是说一个表达式又可以包含多个子表达式。例如在我们定义变量时会写作“int a; ”或者“int a=1; ”,这二者显然是有区别的,后者不但包括前者的“变量定义”操作,而且还多了一步“变量赋值”操作,所以我们可以认为它是“非终极表达式”;而前者则可被视为原子操作,也就是说它是不可再拆分的“终极表达式”。
二、语义树
为了更好地帮助大家理解解释器模式,我们首先发明一种脚本语言,以此开始我们的实战环节。众所周知,网络游戏玩家经常会花费大量的时间来打怪升级,过程漫长而且伤害身体,所以我们研发了一款辅助程序——“滑鼠精灵”,利用它来直接发送指令给鼠标,从而驱动鼠标来实现单击、移动等操作,实现游戏人物自动打怪升级,以此解放玩家的双手。
1. 脚本定义
/**
BEGIN 脚本开始
MOVE 500,600; 鼠标指针移动到坐标(500,600)
BEGIN LOOP 5 开始循环5次
LEFT_CLICK; 循环体内单击左键
DELAY 1; 每次延迟1秒
ENDL 循环体结束
RIGHT_DOWN; 按下右键
DELAY 7200; 延迟2小时
END; 脚本结束
*/
说明:
- 注意每行的脚本注释,玩家首先让鼠标指针移动到地图的某个坐标点上;然后循环单击5次鼠标,每次延迟1秒,引导游戏人物到达刷怪地点;最后按下右键不放,连续释放技能,直到挂机2小时后结束。这样脚本就完成了打怪升级的全自动化操作。
- 基于这个良好的开端,我们可以针对这个脚本进行语法分析了。首先我们要注意第3行的循环指令BEGIN LOOP,它是可以包含任意其他子指令的指令集,所以它是非终极表达式。接下来第4行的单击鼠标左键指令LEFT_CLICK也是非终极表达式,因为单击可以被拆分为“按下”与“松开”两个连续的指令动作。除此之外,其他的指令都应该是不可以再拆分的指令了,也就是说它们都是终极表达式。
三、接口与终极表达式
1. 表达式接口
经过对“滑鼠精灵”脚本中每个表达式的拆分,我们就可以对表达式进行建模了。无论是“终极表达式”还是“非终极表达式”,都是表达式,所以我们应该定义一个表达式接口,对所有表达式进行行为抽象,
public interface Expression
public void interpret();
2.终极表达式
既然表达式接口标准已经确立,那么我们就从最基本的原子操作(终极表达式)开始定义实现类。它们依次是鼠标移动表达式Move、鼠标左键按下表达式LeftKeyDown、鼠标左键松开表达式LeftKeyUp(右键对应的表达式与此类似,读者可自己实现),以及延迟表达式Delay,请分别参看代码
//鼠标移动表达式
public class Move implements Expression
//鼠标指针坐标位置
private int x,y;
public Move(int x, int y)
this.x = x;
this.y = y;
public void interpret()
System.out.println("移动鼠标:【" + x + "," + y + "】");
//鼠标左键按下表达式
public class LeftKeyDown implements Expression
@Override
public void interpret()
System.out.println("按下鼠标: 左键");
//鼠标左键松开表达式
public class LeftKeyUp implements Expression
@Override
public void interpret()
System.out.println("松开鼠标: 左键");
//延迟表达式
public class Delay implements Expression
private int seconds;// 延迟秒数
public Delay(int seconds)
this.seconds = seconds;
public int getSeconds()
return seconds;
@Override
public void interpret()
System.out.println("系统延迟:" + seconds + "秒");
try
Thread.sleep(seconds * 1000);
catch (InterruptedException e)
e.printStackTrace();
说明:
- 所有终极表达式都实现了解释方法interpret(),并进行了自己特有的指令解释操作(以输出模拟)。其中比较特殊的是延迟表达式Delay,它能基于构造器传入的时间长度使当前进程暂停,以模拟系统操作线程的延迟功能。
3.非终极表达式
所有终极表达式至此完成,我们将它们按一定顺序组合起来就是非终极表达式了。例如鼠标左键单击操作一定是由“按下左键”及“松开左键”两个原子操作组合而成,所以左键单击表达式应该包含鼠标左键按下表达式与鼠标左键松开表达式两个子表达式,请参看代码。
public class LeftKeyClick implements Expression
private Expression leftKeyDown;
private Expression leftKeyUp;
public LeftKeyClick()
this.leftKeyDown = new LeftKeyDown();
this.leftKeyUp = new LeftKeyUp();
@Override
public void interpret()
leftKeyDown.interpret();
leftKeyUp.interpret();
说明:
- 单击这种操作不需要对外提供入参构造,所以左键单击表达式LeftKeyClick在构造方法中主动实例化了“鼠标左键按下表达式”与“鼠标左键松开表达式”两个子表达式
接下来,循环表达式相对复杂一些,我们需要知道的是循环次数,以及循环体内具体要解释的子表达式序列,请参看代码。
public class Repetition implements Expression
private int loopCount;// 循环次数
private Expression loopBodySequence;// 循环体内的子表达式序列
public Repetition(int loopCount, Expression loopBodySequence)
this.loopCount = loopCount;
this.loopBodySequence = loopBodySequence;
@Override
public void interpret()
while (loopCount > 0)
loopBodySequence.interpret();
loopCount--;
说明:
- 注意,此处并不关心loopBodySequence中还包含哪些子表达式,循环表达式负责的是迭代操作。
此时读者可能对这个循环体表达式产生了一些疑惑,它到底是一个什么样的表达式类?
public class Sequence implements Expression
private List<Expression> expressions;// 表达式列表
public Sequence(List<Expression> expressions)
this.expressions = expressions;
@Override
public void interpret()
expressions.forEach(exp -> exp.interpret());
说明:
- 我们定义了一个表达式列表List,以此保证多个子表达式的顺序。
- 最后,我们在解释方法interpret()中,按顺序依次对所有子表达式进行了调用。
4.客户端类
public class Client
public static void main(String[] args)
/**
BEGIN 脚本开始
MOVE 500,600; 鼠标指针移动到坐标(500,600)
BEGIN LOOP 5 开始循环5次
LEFT_CLICK; 循环体内单击左键
DELAY 1; 每次延迟1秒
ENDL 循环体结束
RIGHT_DOWN; 按下右键
DELAY 7200; 延迟2小时
END; 脚本结束
*/
Expression sequence = new Sequence(Arrays.asList(
new Move(500,600),
new Repetition(
5,
new Sequence(Arrays.asList(
new LeftKeyClick() , new Delay(1)
))
),
new RightKeyDown(),
new Delay(7200)
));
sequence.interpret();
输出结果:
移动鼠标:【500,600】
按下鼠标: 左键
松开鼠标: 左键
系统延迟:1秒
按下鼠标: 左键
松开鼠标: 左键
系统延迟:1秒
按下鼠标: 左键
松开鼠标: 左键
系统延迟:1秒
按下鼠标: 左键
松开鼠标: 左键
系统延迟:1秒
按下鼠标: 左键
松开鼠标: 左键
系统延迟:1秒
按下鼠标: 右键
系统延迟:7200秒
说明:
- 需要注意的是,语义树的生成是由客户端完成的,其实我们完全可以再设计一个语法分析器(evaluator),它非常类似于编译器(compiler),以实现对各种脚本语言的自动化解析,并完成语义树的自动化生成。
总结
提示:这里对文章进行总结:
-
除了被应用于解释一些相对简单的语法规则,我们还可以利用解释器模式构建一套规则校验引擎,如将解释器接口换作public boolean validate(String target),并由各个实现类返回校验结果,类似于正则表达式的校验引擎。无论如何演变,解释器模式其实就是一种组合模式的特殊应用,它巧妙地利用了组合模式的数据结构,基于上下文生成表达式(解释器)组合起来的语义树,最终通过逐级递进解释完成上下文的解析。
-
解释器模式的各角色定义如下。
-
AbstractExpression(抽象表达式):定义解释器的标准接口interpret(),所有终极表达式类与非终极表达式类均需实现此接口。对应本章例程中的表达式接口Expression。
-
TerminalExpression(终极表达式):抽象表达式接口的实现类,具有原子性、不可拆分性的表达式。对应本章例程中的鼠标移动表达式Move、鼠标左键按下表达式LeftKeyDown、鼠标左键松开表达式LeftKeyUp、延迟表达式Delay。
-
NonTerminalExpression(非终极表达式):抽象表达式接口的实现类,包含一个或多个表达式接口引用,所以它所包含的子表达式可以是非终极表达式,也可以是终极表达式。对应本章例程中的左键单击表达式LeftKeyClick、循环表达式Repetition、表达式序列Sequence。
-
Context(上下文):需要被解释的语言类,它包含符合解释器语法规则的具体语言。对应本例程中的滑鼠精灵脚本MouseScript。
-
Client(客户端):根据语言的语法结构生成对应的表达式语法树,然后调用根表达式的解释方法得到结果。
以上是关于行为篇-解释器模式的主要内容,如果未能解决你的问题,请参考以下文章