解释器模式是一种使用频率较低但是学习难度较大的设计模式,用于描述如何使用面向对象语言构成一个简单的语言解释器。某些情况下可能需要自定义一个新语言,这种语言具有自己的文法规则,这时可以使用解释器模式进行设计,比如模拟机器人的控制程序,每一条指令对应一个动作,通过解释输入的指令来实现对机器人的控制。下面先来看一些术语定义。
文法规则是用于描述语言的语法的规则,比如,汉语中一个句子的文法规则为:
主 谓 宾
这就是句子的文法规则,同样计算机语言也有自己的文法规则。
BNF是Backus-Naur Form
的缩写,是由John Backus
以及Peter Naur
首次引入的一种形式化符号来描述给定语言的语法,BNF中定义的部分符号如下:
::=
:表示定义为
,左边的语言单位可以通过右边进行说明和定义|
:表示或者
"
或'
:双引号或单引号里面的字符串表示字符串本身在以下的模拟描述机器人移动的文法规则中:
expression ::= direction action distance | composite //表达式
composite ::= expression 'and' expression //复合表达式
direction ::= 'up' | 'down' | 'left'| 'right' //移动方向
action ::= 'move' | 'run' //移动方式
distance ::= an integer //移动距离
定义了5条文法规则,对应5个语言单位,这些语言单位可以分为:
direction
或action
除了使用文法规则定义一个语言外,还能使用一种叫抽象语法树的直观方式表示,例如表达式:
1 / 2 * 3 - 4 + 1
可以通过如下抽象语法树定义:
在该抽象语法树中,可以通过终结符value
以及非终结符operation
组成复杂的语句,终结符作为抽象语法树的叶子,非终结符作为非叶子节点,可以将终结符或者包含终结符与非终结符的节点作为子节点。
解释器模式:定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。
这里的语言指的是使用规定格式以及语法的代码。解释器模式是一种类行为型模式。
AbstractExpression
(抽象表达式):声明了抽象的解释操作,是所有终结符表达式以及非终结符表达式的父类TerminalExpression
(终结符表达式):抽象表达式的子类,实现了与文法规则中的终结符相关联的解释操作,句子中的每一个终结符都是该类的一个实例,通常只有少数几个终结符表达式类NonterminalExpression
(非终结符表达式):也是抽象表达式的子类,实现了文法规则中非终结符的解释操作,由于非终结符表达式可以包含非终结符表达式以及终结符表达式,因此一般通过递归方式完成解释Context
(环境类):用于存储解释器之外的一些全局信息,通常它临时存储需要解释的语句这里暂时不需要环境类,为了兼容定义一个空类:
class Context{}
包含抽象解释操作方法:
interface AbstractExpression
{
void interpret(Context context);
}
解释终结符表达式:
class TerminalExpression implements AbstractExpression
{
@Override
public void interpret(Context context)
{
System.out.println("终结符解析");
}
}
class NonterminalExpression implements AbstractExpression
{
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left,AbstractExpression right)
{
this.left = left;
this.right = right;
}
@Override
public void interpret(Context context)
{
System.out.println("非终结符解析");
if(left != null)
left.interpret(context);
if(right != null)
right.interpret(context);
}
}
解释非终结符时一般需要递归处理,这里模拟了非终结符左右两边的表达式操作。
public static void main(String[] args)
{
AbstractExpression expression1 = new TerminalExpression();
AbstractExpression expression2 = new TerminalExpression();
AbstractExpression expression3 = new NonterminalExpression(expression1,expression2);
expression3.interpret(null);
}
定义两个终结符表达式与一个非终结符表达式,最后对非终结符表达式进行解释。
对机器人移动指令进行解释,移动的语法表达如下:方向 方式 距离,方向包括上下左右四个方向,方式包括跑以及一般移动,距离为一个整数,一条移动指令可以组合多条子移动指令,使用解释器模式进行设计。
设计如下:
AbstractNode
DirectionNode
+ActionNode
+DistanceNode
AndNode
+SentenceNode
抽象表达式类如下:
interface AbstractNode
{
String interpret(String str);
}
终结符表达式类:
class DirectionNode implements AbstractNode
{
private static final Map<String,String> strs;
static
{
strs = new HashMap<>();
strs.put("up", "向上");
strs.put("down", "向下");
strs.put("left", "向左");
strs.put("right", "向右");
}
@Override
public String interpret(String str)
{
return strs.containsKey(str) ? strs.get(str) : "无效操作";
}
}
class ActionNode implements AbstractNode
{
private static final Map<String,String> strs;
static
{
strs = new HashMap<>();
strs.put("move", "移动");
strs.put("run", "快速移动");
}
@Override
public String interpret(String str)
{
return strs.containsKey(str) ? strs.get(str) : "无效操作";
}
}
class DistanceNode implements AbstractNode
{
@Override
public String interpret(String str)
{
return str;
}
}
根据对应的字符串返回相应的字符串即可。
非终结符表达式类:
class SentenceNode implements AbstractNode
{
private final AbstractNode direction = new DirectionNode();
private final AbstractNode action = new ActionNode();
private final AbstractNode distance = new DistanceNode();
@Override
public String interpret(String s)
{
String [] str = s.split(" ");
return direction.interpret(str[0])+action.interpret(str[1])+distance.interpret(str[2]);
}
}
class AndNode implements AbstractNode
{
@Override
public String interpret(String s)
{
if(s.contains("and"))
{
int index = s.indexOf("and");
String leftStr = s.substring(0, index-1);
String rightStr = s.substring(index+4);
AbstractNode left = (leftStr.contains("and") ? new AndNode() : new SentenceNode());
AbstractNode right = (rightStr.contains("and") ? new AndNode() : new SentenceNode());
return left.interpret(leftStr) + " 再 " + right.interpret(rightStr);
}
return new SentenceNode().interpret(s);
}
}
其中AndNode
采取了递归进行解释操作,如果分割后的字符串还含有and
则赋值为AndNode
,否则为SentenceNode
。
测试:
public static void main(String[] args)
{
AbstractNode node = new AndNode();
System.out.println(node.interpret("up move 5 and down run 10 and down move 10 and left run -9"));
}
如果项目中需要对数据表达式进行分析与计算,可以直接使用现有的库,比如:
等等,下面以Jep为例演示该库的使用方法。Jep是Java expression parser
的简称,即Java表达式分析器,它是一个用来转换和计算数学表达式的Java库,用户可以以字符串形式输入一个任意公式,然后快速计算出结果。Jep支持用户自定义变量,常量,函数,包括很多常用的数学函数以及常量。
首先下载JAR包依赖,例子如下:
import com.singularsys.jep.*;
public class Test
{
public static void main(String[] args) throws Exception
{
Jep jep=new Jep();
//定义要计算的数据表达式
String interestOnDeposit="本金*利率*时间";
//给相关变量赋值
jep.addVariable("本金",10000);
jep.addVariable("利率",0.038);
jep.addVariable("时间",2);
jep.parse(interestOnDeposit); //解析表达式
Object accrual=jep.evaluate(); //计算
System.out.println("存款利息:"+accrual);
}
}
如果觉得文章好看,欢迎点赞。
同时欢迎关注微信公众号:氷泠之路。