目录
一、解释器模式是什么
【定义】:给定一个语言,定义一个语法的一种表示, 并定义一个解释器, 这个解释器使用该表示来解释语言中的句子。
【特性】:在实际的开发中使用比较少,因为它会引起效率、性能以及维护等问题,它属于行为型模式。
【主要作用】:对于一些固定语法构建一个解释句子的解释器。比如:R = A * B
二、解释器模式的适用场景
-
【适用场景】
-
不太关注执行效率,且语言的语法较为简单。
-
可以用一种简单的语言来表达处理重复问题时。
-
可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
-
-
【实际案例】
-
SQL 解析
-
符号处理引擎
-
XML 文档解释
-
Java中的 Expression4J 或 Jep
-
三、解释器模式结构
解释器模式本质上是对一组特定的语言或者公式进行翻译,比如数学公式可以用Java的Jep。而在解析这些公式的产生的不同行为由不同的角色完成:
-
抽象表达式(Abstract Expression)角色
定义解释器的接口,声明解释方法 interpret()。
-
终结符表达式(Terminal Expression)角色
实现抽象表达式角色。语法中的每一个终结符都有一个具体终结表达式与之相对应。
相当于树的叶子节点,计算到叶子节点则可以返回,
比如R = A * B,A和B就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
-
非终结符表达式(Nonterminal Expression)角色
实现抽象表达式角色,语法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是语法中的运算符或者其他关键字。
相当于树的树枝节点,计算到树枝节点【 * 】则需要往后继续计算
比如公式R = A * B中,【 * 】就是非终结符,解析【 * 】的解释器就是一个非终结符表达式。
-
环境(Context)角色
一般包含各个解释器需要的数据或是公共的功能,用来传递被所有解释器共享的数据。
比如R = A * B,其中A=1,R2=2。1和2的信息需要放在Context中
-
客户端(Client)
将需要解释的表达式转换成使用解释器对象描述的抽象语法树,调用解释器的解释方法。
四、解释器模式实现方式
-
声明一个解释器接口,一般此接口只有一个解释方法interpret()。
-
声明终结符表达式类实现解释器接口,计算到终结符表达式则需要往后继续计算
-
声明非终结符表达式类实现解释器接口,计算到非终结符表达式则需要往后继续计算
-
声明上下文角色,用来传递被所有解释器共享的数据。
五、解释器模式的实现
【案例】:计算器的实现
【案例说明】:
在计算器的案例中,解释器接口作为核心有一个解析计算的接口供具体解释类(加减类)实现, 而计算数据则作为抽象终结符表达式存储(如1+2中的1,2), 计算的运算符则作为非终结符表达式(如1+2中的+),上下文则用来作为变量解析成数字的载体
-
抽象表达式(Abstract Expression)角色
/** * 抽象表达式(Abstract Expression)角色 * @author Edwin * @date 2021/11/22 20:31 */ publicinterface CalculationExpression { /** * 声明解释方法 interpret() * @author Edwin * @date 2021/11/22 20:33 */ int interpret(Context context); }
-
终结符表达式(Terminal Expression)角色
/** * 终结符表达式(Terminal Expression)角色 用来存放数字,或者代表数字的字符 * @author Edwin * @date 2021/11/22 20:35 */ publicclass SymbolicExpression implements CalculationExpression{ /** * 取出变量对应的数据用于计算 这里本质上是叶子结点了 * @author Edwin * @date 2021/11/22 23:14 * @return null */ @Override public int interpret(Context context) { return context.lookup(this); } }
-
抽象非终结符表达式(Nonterminal Expression)角色
这个角色主要用来定义一些通用方法,实际应用中可以根据需要取舍。
/** * 抽象非终结符表达式(Nonterminal Expression)角色 定义公共方法 * @author Edwin * @date 2021/11/22 23:17 */ publicabstractclass NonterminalExpression implements CalculationExpression { /** * 持有一组两个抽象表达式角色的引用,用于参与存储计算的数据 * 使用Java栈(先进后出)遍历栈 * @author Edwin * @date 2021/11/22 22:51 */ private Stack stack; public NonterminalExpression() { this.stack = new Stack(); } /** * 往栈里面存储一个数据 * @author Edwin * @date 2021/11/22 22:51 */ public void push(CalculationExpression data) { stack.push(data); } /** * 从栈里面取出一个数据用于计算 * @author Edwin * @date 2021/11/18 19:40 */ public CalculationExpression pop() { return (CalculationExpression)stack.pop(); } /** * 判断是否栈空了 * @author Edwin * @date 2021/11/18 19:40 */ public Boolean empty() { return stack.empty(); } }
-
非终结符表达式(Nonterminal Expression)角色
/** * 非终结符表达式 :减法表达式实现类 * @author Edwin * @date 2021/11/22 22:26 * @return null */ publicclass MinusOperation extends NonterminalExpression{ /** * 持有两个抽象表达式角色 的引用 * 这里传进来的是终结符表达式角色,也就是树叶节点,直接计算返回 * @author Edwin * @date 2021/11/22 22:41 */ private CalculationExpression expression1; private CalculationExpression expression2; public MinusOperation(CalculationExpression expression1, CalculationExpression expression2) { this.expression1 = expression1; this.expression2 = expression2; } /** * 减法 无法用栈实现 通过构造函数实现 * @author Edwin * @date 2021/11/22 22:44 */ @Override public int interpret(Context context) { returnthis.expression1.interpret(context) - this.expression2.interpret(context); } } /** * 非终结符表达式 :加法表达式实现类 * @author Edwin * @date 2021/11/22 22:26 * @return null */ publicclass PlusOperation extends NonterminalExpression{ /** * 加法 也可以跟减法一样使用构造函数实现 * 这里只是演示Java栈的实现方式 * @author Edwin * @date 2021/11/22 22:44 */ @Override public int interpret(Context context) { int interpret = 0; while (true){ if (this.empty()) { //栈空了!没有数据可以参与运算了 break; } interpret += this.pop().interpret(context); } return interpret; } }
-
环境(Context)角色
/** * 上下文角色 * @author Edwin * @date 2021/11/22 20:18 */ publicclass Context { /** * 通过Map存储 (变量:数据) * @author Edwin * @date 2021/11/22 23:08 */ private Map<CalculationExpression, Integer> map = new HashMap<>(); /** * 定义变量,并未变量赋值 * @author Edwin * @date 2021/11/22 23:07 */ public void add(CalculationExpression calculationExpression, Integer value){ map.put(calculationExpression, value); } /** * 取出变量对应的数据 * @author Edwin * @date 2021/11/22 23:08 */ public int lookup(CalculationExpression calculationExpression){ return map.get(calculationExpression); } }
-
客户端代码实现
public static void main(String[] args) { //初始化上下文 计算 5+5-3的结果 Context context = new Context(); // 初始化终结符表达式 变量 SymbolicExpression expression1 = new SymbolicExpression(); SymbolicExpression expression2 = new SymbolicExpression(); SymbolicExpression expression3 = new SymbolicExpression(); //为变量赋值 context.add(expression1, 5); context.add(expression2, 5); context.add(expression3, 3); //为加法中的栈赋值 PlusOperation plusOperation = new PlusOperation(); plusOperation.push(expression1); plusOperation.push(expression2); //计算5+5-3 int interpret = new MinusOperation(plusOperation, expression3).interpret(context); System.out.println("表达式 5+5-3=" + interpret); }
-
案例输出结果
六、解释器模式的优缺点
-
优点
-
可扩展性比较好,可以通过继承等机制来改变或扩展文法。
-
易于实现简单文法,语法树中的每个表达式节点类都是相似的。
-
-
缺点
-
执行效率较低,对于复杂的表达式通常使用大量的循环和递归调用
-
会引起类膨胀,需要给每条规则至少定义一个类。
-
七、解释器模式和其他模式的区别
【解释器模式】:通过解释器来解释这个语言中的句子或表达式的方式,是对语法的操作。
【适配器模式】:让接口不同的类通过适配器模式可以一起工作,针对接口的操作。
八、总结
在解释器模式模式中经常分不清表达式里面【终结符】和【非终结符】,以至于我们常常对概念比较混乱,实际上我们这一定程度上将一个表达式当成一个树状结构来看待会清晰很多,【终结符】当成叶子结点,【非终结符】当成树枝节点,遇到非终结符要继续往下解析,遇到终结符则返回。如下: