第六章 7.0 解释器模式

分类:01_设计模式

标签:

7.0.1 解释器模式介绍

解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、SQL 解析等。不过,了解它的实现原理同样很重要,能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。

解释器模式(Interpreter pattern)的原始定义是:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。

我们通过一个例子给大家解释一下解释器模式


//用于两个整数相加的方法
public static int add(int a , int  b){
   return a + b;
}

//用于三个整数相加的方法
public static int add(int a , int  b,int c){
   return a + b + c;
}

public static int add(Integer ... arr){
   int sum = 0;
   for(Integer num : arr){
       sum += num;
   }
   return sum;
}

+ -

上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如: 5-3+2-1, 10-5+20....

文法规则和抽象语法树

解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子.

在上面提到的加法/减法解释器中,每一个输入表达式(比如:2+3+4-5) 都包含了3个语言单位,可以使用下面的文法规则定义:

文法是用于描述语言的语法结构的形式规则。

expression ::= value | plus | minus  
plus ::= expression ‘+’ expression    
minus ::= expression ‘-’ expression   
value ::= integer

注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。

上面规则描述为 :

表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。

抽象语法树:

在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例,例如加法/减法表达式语言中的语句 " 1+ 2 + 3 - 4 + 1" 可以通过下面的抽象语法树表示

127.jpg

7.02 解释器模式原理

128.jpg

解释器模式包含以下主要角色。

7.0.3 解释器模式实现

为了更好的给大家解释一下解释器模式, 我们来定义了一个进行加减乘除计算的“语言”,语法规则如下:

我们举个例子来解释一下上面的语法规则:

代码示例:


/**
* 表达式解释器类
* @author spikeCong
* @date 2022/10/20
**/
public class ExpressionInterpreter {

   //Deque双向队列,可以从队列的两端增加或者删除元素
  private Deque<Long>  numbers = new LinkedList<>();

 
  //接收表达式进行解析
  public long interpret(String expression){

      String[] elements = expression.split(" ");

      int length = elements.length;

      //获取表达式中的数字
      for (int i = 0; i < (length+1)/2; ++i) {
          //在 Deque的尾部添加元素
          numbers.addLast(Long.parseLong(elements[i]));
      }

      //获取表达式中的符号
      for (int i = (length+1)/2; i < length; ++i) {
          String operator = elements[i];
          //符号必须是 + - * / 否则抛出异常
          boolean isValid = "+".equals(operator) || "-".equals(operator)
                  || "*".equals(operator) || "/".equals(operator);
          if (!isValid) {
              throw new RuntimeException("Expression is invalid: " + expression);
          }

           //pollFirst()方法, 移除Deque中的第一个元素,并返回被移除的值
          long number1 = numbers.pollFirst(); //数字
          long number2 = numbers.pollFirst();

          long result = 0;  //运算结果

          //对number1和number2进行运算
          if (operator.equals("+")) {
              result = number1 + number2;
          } else if (operator.equals("-")) {
              result = number1 - number2;
          } else if (operator.equals("*")) {
              result = number1 * number2;
          } else if (operator.equals("/")) {
              result = number1 / number2;
          }

          //将运算结果添加到集合头部
          numbers.addFirst(result);
      }

      //运算完成numbers中应该保存着运算结果,否则是无效表达式
      if (numbers.size() != 1) {
          throw new RuntimeException("Expression is invalid: " + expression);
      }
      //移除Deque的第一个元素,并返回
      return numbers.pop();
  }
}

代码重构

上面代码的所有的解析逻辑都耦合在一个函数中,这样显然是不合适的。这 个时候,我们就要考虑拆分代码,将解析逻辑拆分到独立的小类中, 前面定义的语法规则有两类表达式,一类是数字,一类是运算符,运算符又包括加减乘除。 利用解释器模式,我们把解析的工作拆分到以下五个类:plu,sub,mul,div


/**
* 表达式接口
* @author spikeCong
* @date 2022/10/20
**/
public interface Expression {

   long interpret();
}

/**
* 数字表达式
* @author spikeCong
* @date 2022/10/20
**/
public class NumExpression implements Expression {

   private long number;

   public NumExpression(long number) {
       this.number = number;
   }

   public NumExpression(String number) {
       this.number = Long.parseLong(number);
   }

   @Override
   public long interpret() {
       return this.number;
   }
}

/**
* 加法运算
* @author spikeCong
* @date 2022/10/20
**/
public class PluExpression implements Expression{

   private Expression exp1;
   private Expression exp2;

   public PluExpression(Expression exp1, Expression exp2) {
       this.exp1 = exp1;
       this.exp2 = exp2;
   }

   @Override
   public long interpret() {
       return exp1.interpret() + exp2.interpret();
   }
}

/**
* 减法运算
* @author spikeCong
* @date 2022/10/20
**/
public class SubExpression implements Expression {

   private Expression exp1;
   private Expression exp2;

   public SubExpression(Expression exp1, Expression exp2) {
       this.exp1 = exp1;
       this.exp2 = exp2;
   }

   @Override
   public long interpret() {
       return exp1.interpret() - exp2.interpret();
   }
}

/**
* 乘法运算
* @author spikeCong
* @date 2022/10/20
**/
public class MulExpression implements Expression {

   private Expression exp1;
   private Expression exp2;

   public MulExpression(Expression exp1, Expression exp2) {
       this.exp1 = exp1;
       this.exp2 = exp2;
   }

   @Override
   public long interpret() {
       return exp1.interpret() * exp2.interpret();
   }
}

/**
* 除法
* @author spikeCong
* @date 2022/10/20
**/
public class DivExpression implements Expression {

   private Expression exp1;
   private Expression exp2;

   public DivExpression(Expression exp1, Expression exp2) {
       this.exp1 = exp1;
       this.exp2 = exp2;
   }

   @Override
   public long interpret() {
       return exp1.interpret() / exp2.interpret();
   }
}

//测试
public class Test01 {

   public static void main(String[] args) {

       ExpressionInterpreter e = new ExpressionInterpreter();
       long result = e.interpret("6 2 3 2 4 / - + *");
       System.out.println(result);
   }
}


7.0.4 解释器模式总结

1) 解释器优点

2) 解释器缺点

3) 使用场景


修改内容