定义
解释器模式是一种按照规定语法进行解析的方案。给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
通用类图
暂无(图床暂不能使用)
1)AbstractExpression
具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和NonterminalExpression完成。
2)TerminalExpression
实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。具体到例子中就是VarExpression类,表达式中的每个终结符都在栈中产生了一个VarExpression对象。
3)NonterminalExpression
文法中的每条规则对应于一个非终结表达式,具体到例子中就是加减法规则分别对应到AddExpression和SubExpression两个类。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
4)Context
具体到例子中是采用HashMap代替。
通用案例
/**
* 情景类
* TODO Client是一个封装类,封装的结果就是传递进来一个规范语法文件,解析器分析后产生结果并返回,避免了调用者与语法解析器的耦合关系.
*/
public class Client1 {
public static void main(String[] args) {
/*Context ctx = new Context();
//通常定义一个语法容器,容纳一个具体的表达式,通常为ListArray,LinkedList,Stack等类型.
Stack<Expression> stack = null;
for(;;){
//进行语法判断,并产生递归调用
}
//产生一个完整的语法树,由各个具体的语法分析进行解析
Expression exp = stack.pop();
//具体元素进入场景
exp.interpreter(ctx);*/
}
}
/**
* 环境角色
* 比如可以用HashMap代替
*/
class Context{
}
/**
* 抽象表达式(通常只有一个方法)
* TODO 抽象表达式是生成语法集合(也叫做语法树)的关键,每个语法集合完成指标语法解析任务,它是通过递归调用的方式,最终由最小的语法单元进行解析完成.
*/
abstract class Expression{
//每个表达式必须有一个解析任务
abstract Object interpreter(Context context);
}
/**
* 终结符表达式
* TODO 主要是处理场景元素和数据的转换.
*/
class TerminalExpression extends Expression{
//通常终结符表达式只有一个,但是有多个对象.
@Override
Object interpreter(Context context) {
return null;
}
}
/**
* 非终结符表达式
* TODO 每个非终结符表达式都代表了一个文法规则,并且每个文法规则都只关心自己周边的文法规则的结果(注意是结果),
* TODO 因此这就产生了每个非终结符表达式调用自己周边的非终结符表达式,
* TODO 然后最终、最小的文法规则就是终结符表达式,终结符表达式的概念就是如此,不能够再参与比自己更小的文法运算了.
*/
class NonterminalExpression extends Expression{
//每个非终结符表达式都会对其他表达式产生依赖
public NonterminalExpression(Expression... expressions) {
}
@Override
Object interpreter(Context context) {
//进行文法处理
return null;
}
}
加减法案例
类图
暂无(图床暂不能使用)
案例
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Stack;
/**
* 情景类:加减法模拟类
*/
public class Client2 {
public static void main(String[] args) throws IOException{
String expStr = getExpStr();
//赋值
HashMap<String,Integer> var = getValue(expStr);
Calculator cal = new Calculator(expStr);
System.out.println("运算结果为:" + expStr + "=" + cal.run(var));
}
//获得表达式
public static String getExpStr() throws IOException {
System.out.println("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//获得值映射
public static HashMap<String,Integer> getValue(String expStr) throws IOException{
HashMap<String,Integer> map = new HashMap<>();
//解析有几个参数要传递
for(char ch:expStr.toCharArray()){
if(ch != '+' && ch != '-'){
//解决重复参数的问题
if(!map.containsKey(String.valueOf(ch))){
System.out.println("请输入" + ch + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch),Integer.valueOf(in));
}
}
}
return map;
}
}
/**
* 抽象表达式
*/
abstract class Expression{
//解析公式和数值,其中var中的key值是公式中的参数,value值是具体的数字
abstract int interpreter(HashMap<String,Integer> var);
}
/**
* 变量解析器
*/
class VarExpression extends Expression{
private String key;
public VarExpression(String key) {
this.key = key;
}
@Override
int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
/**
* 抽象运算符号解析器
*/
abstract class SymbolExpression extends Expression{
protected Expression left;
protected Expression right;
//所有的解析公式都应只关心自己左右两个表达式的结果
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}
/**
* 加法解析器
*/
class AddExpression extends SymbolExpression{
public AddExpression(Expression left, Expression right) {
super(left, right);
}
//把左右两个表达式运算的结果加起来
@Override
int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
/**
* 减法解析器
*/
class SubExpression extends SymbolExpression{
public SubExpression(Expression left, Expression right) {
super(left, right);
}
//把左右两个表达式运算的结果相减
@Override
int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
/**
* 解析器封装类
*/
class Calculator {
//定义表达式
private Expression expression;
//构造函数传参,并解析
public Calculator(String expStr){
//定义一个栈,安排运算的先后顺序
Stack<Expression> stack = new Stack<>();
//表达式拆分为字符数组
char[] charArray = expStr.toCharArray();
//运算
Expression left;
Expression right;
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]){
case '+'://加法
//加法结果放到栈中
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new AddExpression(left,right));
break;
case '-'://减法
//减法结果放到栈中
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left,right));
break;
default: //公式中的变量
stack.push(new VarExpression(String.valueOf(charArray[i])));
}
}
this.expression = stack.pop();
}
//开始运算
public int run(HashMap<String,Integer> var){
return this.expression.interpreter(var);
}
}
请输入表达式:
a+b-c+d
请输入a的值:
1
请输入b的值:
2
请输入c的值:
3
请输入d的值:
1
运算结果为:a+b-c+d=1
优点
1)解释器是一个简单的语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。
2)该模式使用类来表示文法规则,可使用继承(依赖倒置原则)来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。
缺点
1)解释器模式会引起类膨胀
每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
2)解释器模式采用递归调用方法
每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。比如如果要排查一个语法错误,那么需要debug一个个的调试下去,直到最小的语法单元。
3)效率问题
解释器模式由于使用了大量的循环和递归,效率是一个不容忽视的问题,特别是一用于解析复杂、冗长的语法时,效率是难以忍受的。
使用场景
1)当需要实现一个简单的语言时,可以使用语言来描述的(比如机器人)。
2)重复发生的问题(比如每天要分析的大量日志,日志格式-非终结符表达式不同-需要制定,但数据要素-终结符表达式是相同的)。
3)当有一个简单的语法,而且简单比效率更重要时,一个简单语法需要解释的场景(比如SQL语法)。
4)可以处理脚本语言和编程语言。
5)当语法规则的数目太大时,这个模式可能会变得非常繁杂。在这种情况下,使用解析器/编译器的产生器可能更合适。
注意事项
建议当文法非常复杂时,使用其他的技术如语法分析程序或编译器生成器来处理。
标题:设计模式(二十一)行为模式(类行为型模式)解释器模式-INTERPRETER
作者:yazong
地址:https://blog.llyweb.com/articles/2020/09/16/1600269454190.html