解释器模式在Spring源码中的应用

行为型模式

目录

1、解释器模式(Interpreter Pattern)

1.1 解释器模式UML图

1.2 日常生活中看解释器模式

1.3 Java代码实现

2、解释器模式在Spring源码中体现

3、解释器模式优缺点

3.1 优点

3.2 缺点

3.3 使用场景

3.4 注意事项


1、解释器模式(Interpreter Pattern)

提供了评估语言的语法或表达式的方式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

  • 意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
  • 主要解决:对于一些固定文法构建一个解释句子的解释器。
  • 何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
  • 如何解决:构建语法树,定义终结符与非终结符。
  • 关键代码:构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。

1.1 解释器模式UML图

010512_Qfao_2003960.png

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图

interpreteruml

下面试着帮大家深入理解这个模式,首先是非终结符和,终结符的理解方式,
非终结符表达式(相当于树的树杈):在这个例子中就是相加,相减的表达式,称为非终结符,这是非常形象的,因为当运算遇到这类的表达式的时候,必须先把非终结符的结果计算出来,犹如剥茧一般,一层一层的调用,就比如上面的 

new MinusOperation(new PlusOperation(a,b), c).interpreter(context)

这个MinusOperation左边参数是new PlusOperation(a,b),是非终结符表达式,所以要调用PlusOperation,因为PlusOperation的左右两边都是TerminalExpression,是终结符表达式,所以计算然后返回,到最外面的MinusOperation函数,发现右边c是终结符表达式,所以可以计算。

终结符表达式(相当于树的叶子):遇到这个表达式interpreter执行能直接返回结果,不会向下继续调用。

用构建的语法树可以直观的

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 使用场景

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  2. 一些重复出现的问题可以用一种简单的语言来进行表达。
  3. 一个简单语法需要解释的场景。
  4. 编译器
  5. 运算表达式计算、正则表达式
  6. 机器人

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值