行为型模式
目录
1、解释器模式(Interpreter Pattern)
提供了评估语言的语法或表达式的方式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
- 意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
- 主要解决:对于一些固定文法构建一个解释句子的解释器。
- 何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
- 如何解决:构建语法树,定义终结符与非终结符。
- 关键代码:构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。
1.1 解释器模式UML图
1.2 日常生活中看解释器模式
编译器、运算表达式计算。
1.3 Java代码实现
解释器模式最好的例子还是计算器,用解释器模式来构建一个简单的计算器
/***
*
*@Author ChenjunWang
*@Description:解释器接口
*@Date: Created in 16:20 2018/4/17
*@Modified By:
*
*/
public interface Expression {
int interpreter(Context context);//一定会有解释方法
}
/***
*
*@Author ChenjunWang
*@Description:抽象非终结符表达式
*@Date: Created in 16:22 2018/4/17
*@Modified By:
*
*/
public abstract class NonTerminalExpression implements Expression{
Expression e1,e2;
public NonTerminalExpression(Expression e1, Expression e2){
this.e1 = e1;
this.e2 = e2;
}
}
/***
*
*@Author ChenjunWang
*@Description:减法表达式实现类
*@Date: Created in 16:57 2018/4/17
*@Modified By:
*
*/
public class MinusOperation extends NonTerminalExpression {
public MinusOperation(Expression e1, Expression e2) {
super(e1, e2);
}
//将两个表达式相减
@Override
public int interpreter(Context context) {
return this.e1.interpreter(context) - this.e2.interpreter(context);
}
}
/***
*
*@Author ChenjunWang
*@Description:终结符表达式(在这个例子,用来存放数字,或者代表数字的字符)
*@Date: Created in 16:22 2018/4/17
*@Modified By:
*
*/
public class TerminalExpression implements Expression{
String variable;
public TerminalExpression(String variable){
this.variable = variable;
}
@Override
public int interpreter(Context context) {
return context.lookup(this);
}
}
/***
*
*@Author ChenjunWang
*@Description:
*@Date: Created in 16:56 2018/4/17
*@Modified By:
*
*/
public class PlusOperation extends NonTerminalExpression {
public PlusOperation(Expression e1, Expression e2) {
super(e1, e2);
}
//将两个表达式相加
@Override
public int interpreter(Context context) {
return this.e1.interpreter(context) + this.e2.interpreter(context);
}
}
/***
*
*@Author ChenjunWang
*@Description:上下文类(这里主要用来将变量解析成数字【当然一开始要先定义】)
*@Date: Created in 16:48 2018/4/17
*@Modified By:
*
*/
public class Context {
private Map<Expression, Integer> map = new HashMap<>();
//定义变量
public void add(Expression s, Integer value){
map.put(s, value);
}
//将变量转换成数字
public int lookup(Expression s){
return map.get(s);
}
}
/***
*
*@Author ChenjunWang
*@Description:测试类
*@Date: Created in 13:27 2018/4/8
*@Modified By:
*
*/
public class Test {
public static void main(String[] args) {
Context context = new Context();
TerminalExpression a = new TerminalExpression("a");
TerminalExpression b = new TerminalExpression("b");
TerminalExpression c = new TerminalExpression("c");
context.add(a, 4);
context.add(b, 8);
context.add(c, 2);
System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
}
}
运行结果如下
-----------------------------------
10
UML图
下面试着帮大家深入理解这个模式,首先是非终结符和,终结符的理解方式,
非终结符表达式(相当于树的树杈):在这个例子中就是相加,相减的表达式,称为非终结符,这是非常形象的,因为当运算遇到这类的表达式的时候,必须先把非终结符的结果计算出来,犹如剥茧一般,一层一层的调用,就比如上面的
new MinusOperation(new PlusOperation(a,b), c).interpreter(context)
这个MinusOperation左边参数是new PlusOperation(a,b),是非终结符表达式,所以要调用PlusOperation,因为PlusOperation的左右两边都是TerminalExpression,是终结符表达式,所以计算然后返回,到最外面的MinusOperation函数,发现右边c是终结符表达式,所以可以计算。
终结符表达式(相当于树的叶子):遇到这个表达式interpreter执行能直接返回结果,不会向下继续调用。
用构建的语法树可以直观的
叶子节点即为终结符,树杈即为非终结符,遇到非终结符要继续往下解析,遇到终结符则返回。a+b-c(4+8-2)
看到这如果你已经懂了的话,那就不必再往下看了,下面是拓展,上面的语法树是LZ手动建的(new MinusOperation(new PlusOperation(a,b), c).interpreter(context)),实际情况,客户输入的都是(4+8-2)这样的式子,所以,LZ接下来写的就是解析的输入式子然后自动构建语法树,然后计算结果拉。
/***
*
*@Author ChenjunWang
*@Description:
*@Date: Created in 16:48 2018/4/17
*@Modified By:
*
*/
public class Context {
private Map<Expression, Integer> map = new HashMap<>();
public void add(Expression s, Integer value){
map.put(s, value);
}
public Integer lookup(Expression s){
return map.get(s);
}
//构建语法树的主要方法
public static Expression build(String str) {
//主要利用栈来实现
Stack<Expression> objects = new Stack<>();
for (int i = 0; i < str.length(); i++){
char c = str.charAt(i);
//遇到运算符号+号时候
if (c == '+'){
//先出栈
Expression pop = objects.pop();
//将运算结果入栈
objects.push(new PlusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i)))));
} else if (c == '-'){
//遇到减号类似加号
Expression pop = objects.pop();
objects.push(new MinusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i)))));
} else {
//遇到非终结符直接入栈(基本就是第一个数字的情况)
objects.push(new TerminalExpression(String.valueOf(str.charAt(i))));
}
}
//把最后的栈顶元素返回
return objects.pop();
}
}
/***
*
*@Author ChenjunWang
*@Description:终结符实现类
*@Date: Created in 16:22 2018/4/17
*@Modified By:
*
*/
public class TerminalExpression implements Expression {
String variable;
public TerminalExpression(String variable){
this.variable = variable;
}
@Override
public int interpreter(Context context) {
//因为要兼容之前的版本
Integer lookup = context.lookup(this);
if (lookup == null)
//若在map中能找到对应的数则返回
return Integer.valueOf(variable);
//找不到则直接返回(认为输入的就是数字)
return lookup;
}
}
/***
*
*@Author ChenjunWang
*@Description:测试类
*@Date: Created in 13:27 2018/4/8
*@Modified By:
*
*/
public class Test {
public static void main(String[] args) {
Context context = new Context();
TerminalExpression a = new TerminalExpression("a");
TerminalExpression b = new TerminalExpression("b");
TerminalExpression c = new TerminalExpression("c");
String str = "4+8-2+9+9-8";
Expression build = Context.build(str);
System.out.println("4+8-2+9+9-8=" + build.interpreter(context));
context.add(a, 4);
context.add(b, 8);
context.add(c, 2);
System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
}
}
运行结果如下
-----------------------------------------
4+8-2+9+9-8=20
10
当然实现的这个计算器不是非常的好,只是为了说明解释器模式,望各位看官见谅哈,若各位有兴趣可以继续完善这个demo,比如增加乘法和除法,对输入的数据进行判断,还有目前对字符串解析的计算肯定只支持(0-9)的数字计算等等,大家可以在这些点上进行拓展。
2、解释器模式在Spring源码中体现
在 Spring 中,ExpressionParser 接口内部采用的也是解释器模式,它封装了字符串表达式的语法,源码如下。
package org.springframework.expression;
public interface ExpressionParser {
Expression parseExpression(String expressionString) throws ParseException;
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
ExpressionParser 不知道大家有没有用过,这里面也可以进行加减乘除运算。这里我们不深入讲解源码。如下是一个简单的应用示例:
package net.biancheng.c.interpreter;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class Test {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("100*2+400*1+66");
int result = (Integer) expression.getValue();
System.out.println("计算结果是:" + result);
}
}
运行结果如下。
计算结果是:666
由运行结果可以看到,运行结果与预期结果是一致的。
解释器模式一般在业务开发中用的相对较少,常用的表达式都有人解析好了,直接用就可以了,除非我们从事底层开发需要自己去定义较为复杂的表达式。
3、解释器模式优缺点
3.1 优点
- 1、可扩展性比较好,灵活。
- 2、增加了新的解释表达式的方式。
- 3、易于实现简单文法。
3.2 缺点
- 1、可利用场景比较少。
- 2、对于复杂的文法比较难维护。维护成本高
- 3、解释器模式会引起类膨胀。 过于复杂时效率低
- 4、解释器模式采用递归调用方法。
3.3 使用场景
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
- 一些重复出现的问题可以用一种简单的语言来进行表达。
- 一个简单语法需要解释的场景。
- 编译器
- 运算表达式计算、正则表达式
- 机器人
3.4 注意事项
可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。
参考文章:
https://blog.csdn.net/niunai112/article/details/79982712
http://c.biancheng.net/view/8505.html
https://blog.csdn.net/niunai112/article/details/79982712
https://www.runoob.com/design-pattern/interpreter-pattern.html