写点什么

用 JAVA 捋一下设计模式 23- 解释器模式

作者:下雨了
  • 2022 年 4 月 03 日
  • 本文字数:7510 字

    阅读完需:约 25 分钟

概念及定义

解释器模式在设计模式中利用率是比较低的,且也是比较难以理解的。你可以创造一种新的具有固定语法规则和语义的语言,你可以利用解释器模式来还原这门语言。例如可以将3*2+(2*(3+4))*3/2+(2*2)-7这行字符串还原成 Java 可以理解的数字运算。

解释器模式中有几个概念:

  1. 非终结符:语言中可拆分的元素。2*(3+4)即为非终结符。

  2. 终结符:语言中不可拆分的最小元素。2即为终结符。

  3. 文法:类似于语法,每种语言都有自己的规则。例子中+-*/不能连续出现。

  4. 语句:由终结符构成的符合文法的基本单位。3*2即为语句。

  5. 语法树: 语句的组成结构的树形表示。


解释器模式的主要思想

工厂类的生产接口以枚举类为参数,在内部根据枚举类生产出对应的产品并返回给调用者。


解释器模式中角色以及职责

  1. AbstractExpression(抽象表达式)

TerminalExpression,NonterminalExpression 的抽象父类,定义了用于语义解释以及语法解释的公共解释方法。


  1. TerminalExpression(终结符表达式)

用于解释目标语言中的终结符。


  1. NonterminalExpression(非终结符表达式)

用于解释目标语言中的非终结符。


  1. Context(环境类)

环境类定义了一些上下文信息。


  1. Client(客户端)

用来封装解释器的操作。提供统一的对外方法。


解释器模式的优缺点

优点:

  1. 在处理有一定规律且重复性的数据的时候,可以利用解释器模式进行解析。

  2. 对文法的修改以及扩展比较方便。

缺点:

  1. 一般在解释器中会使用到大量迭代以及递归,执行效率低。

  2. 难以理解,利用率较低。


我们在这就利用解释器模式对3*2+(2*(3+4))*3/2+(2*2)-7这行字符串进行解释,且利用 Java 计算出结果。

实现步骤:

  1. 定义一个双向迭代器接口,用于向前或者向后对字符串进行遍历。

/** * 双向迭代器接口 */public interface Iterator<E> {    //获取第一个元素    public E getFirst();
//获取最后一个元素 public E getEnd();
//是否有下个元素 public boolean hasNext();
//是否有前一个元素 public boolean hasPrevious();
//返回当前指向元素 public E getCurrent();
//返回当前指向元素index public Integer getCurrentIndex();
//迭代器指向下一个元素,并返回该元素 public E getNext();
//迭代器指向上一个元素,并返回该元素 public E getPrevious();}
复制代码


import java.util.List;
public class GeneralIterator<E> implements Iterator<E>{
private List<E> list;
private Integer curIndex;
public GeneralIterator(List<E> list) { this.list = list; curIndex = -1; }
public List<E> getList() { return list; }
public void setList(List<E> list) { this.list = list; }
public Integer getCurIndex() { return curIndex; }
public void setCurIndex(Integer curIndex) { this.curIndex = curIndex; }
@Override public E getFirst() { curIndex = 0; return list.get(curIndex); }
@Override public E getEnd() { curIndex = list.size()-1; return list.get(curIndex); }
@Override public boolean hasNext() { return curIndex < list.size() - 1; }
@Override public boolean hasPrevious() { return curIndex > 0; }
@Override public E getCurrent() { return list.get(curIndex); }
@Override public Integer getCurrentIndex() { return curIndex; }
@Override public E getNext() { E obj = list.get(++curIndex); if(obj==null){ System.out.println("越界"); } return obj; }
@Override public E getPrevious() { E obj = list.get(--curIndex); if(obj==null){ System.out.println("越界"); } return obj; }}
复制代码

2.定义一个计算器类(环境类):

import java.util.ArrayList;import java.util.List;
public class Computer { //原始表达式字符串 private final String originStr;
//原始表达式字符串对应的Character数组 private final List<Character> originChars;
//进行一次数学运算的左操作数 private String leftOperateNumber;
//进行一次数学运算的右操作数 private String rightOperateNumber;
//进行一次数学运算的操作符 private String operator;
public Computer(String originStr) { this.originStr = originStr.replace(" ",""); char[] chars = originStr.toCharArray(); originChars= new ArrayList<>(); for(char c:chars){ originChars.add(c); } }
public GeneralIterator<Character> getIterator() { return new GeneralIterator<>(originChars); }
public String getOriginStr() { return originStr; }
public List<Character> getOriginChars() { return originChars; }
public String getLeftOperateNumber() { return leftOperateNumber; }
public void setLeftOperateNumber(String leftOperateNumber) { this.leftOperateNumber = leftOperateNumber; }
public String getRightOperateNumber() { return rightOperateNumber; }
public void setRightOperateNumber(String rightOperateNumber) { this.rightOperateNumber = rightOperateNumber; }
public String getOperator() { return operator; }
public void setOperator(String operator) { this.operator = operator; }}
复制代码

3.抽象表达式接口

public interface IExpression {    public String interpreter(Computer computer);}
复制代码

4.自定义异常类

public class GrammaticalErrorException extends RuntimeException{    public GrammaticalErrorException(String msg) {        super(msg);    }}
复制代码

5.定义语法 Expression 对语法进行检测

public class GrammarExpression implements IExpression{
private Boolean frontIsOperator = true;
private Integer bracketCheck = 0;
private Integer operatorCount =0;
/** * 表达式语法检测 * @param computer * @return */ @Override public String interpreter(Computer computer) { GeneralIterator<Character> iterator = computer.getIterator(); while(iterator.hasNext()){ Character curChar = iterator.getNext(); switch (curChar){ case '+': case '-': case '*': case '/': if(frontIsOperator){ throw new GrammaticalErrorException("表达式操作符使用错误。"); } frontIsOperator = true; operatorCount++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': frontIsOperator = false; break; case '(': bracketCheck ++; frontIsOperator = true; break; case ')': bracketCheck --; if(bracketCheck<0){ throw new GrammaticalErrorException("表达式括号语法校验错误。"); } if(frontIsOperator){ throw new GrammaticalErrorException("表达式操作符使用错误。"); } break; default: throw new GrammaticalErrorException("表达式含有非法字符。"); }
} if(bracketCheck!=0){ throw new GrammaticalErrorException("表达式括号数量不匹配。"); } if(frontIsOperator){ throw new GrammaticalErrorException("表达式末尾不能是操作符"); } if(operatorCount==0){ throw new GrammaticalErrorException("表达式不能没有操作符"); } return new PriorityExpression().interpreter(computer); }}
复制代码

6.定义优先级 Expression 对字符串进行优先级排序。

import java.util.ArrayList;import java.util.List;
public class PriorityExpression implements IExpression{ @Override public String interpreter(Computer computer) { String str = computer.getOriginStr(); //循环处理含有 (,) 运算符的表达式,直至不包含 (,) 运算符 while(str.contains("(")){ StringBuilder sb = new StringBuilder(str); int firstIndexOfRightBracket = str.indexOf(")"); String substring = str.substring(0, firstIndexOfRightBracket); int lastIndexOfLeftBracket = substring.lastIndexOf("("); String oneCalculate = sb.substring(lastIndexOfLeftBracket + 1, firstIndexOfRightBracket); String afterCalculate = computeWithOutBracket(oneCalculate,computer); StringBuilder beforeSb = new StringBuilder(sb.substring(0, lastIndexOfLeftBracket)); StringBuilder afterInterpreterSb = beforeSb.append(afterCalculate).append(sb.substring((firstIndexOfRightBracket + 1), sb.length())); str = afterInterpreterSb.toString(); } return computeWithOutBracket(str,computer); }
/** * 返回从左至右,stringBuffer中operators中元素第一次出现的index * 如果stringBuffer中不含有任何operators中的元素则返回-1 * @param stringBuffer * @param operators */ private Integer indexPriorOperator(StringBuffer stringBuffer, List<String> operators){ Integer index = -1; for(String operator:operators){ int i = stringBuffer.indexOf(operator); if(i!=-1){ if(index.equals(-1)){ index = i; }else { index = i<index ? i:index; } } } return index; }
/** * 获取str的双向迭代器 * @param str */ public Iterator<Character> getStrIterator(String str) { char[] chars = str.toCharArray(); List<Character> charList= new ArrayList<>(); for(char c:chars){ charList.add(c); } return new GeneralIterator<>(charList); }

/** * 计算不包含 '(' , ')' 的表达式 * @param str * @param computer */ private String computeWithOutBracket(String str,Computer computer){ ComputeExpression computeExpression = new ComputeExpression(); StringBuffer sb = new StringBuffer(str); ArrayList<String> priorOperatorList = new ArrayList<String>() {{ add("*"); add("/"); }}; ArrayList<String> nonPriorOperatorList = new ArrayList<String>() {{ add("+"); add("-"); }}; Integer priorOperatorIndex= indexPriorOperator(sb,priorOperatorList); // 循环处理含有 *,/ 运算符的表达式,直至不包含 *,/ 运算符 while(!priorOperatorIndex.equals(-1)){ String operator = String.valueOf(str.charAt(priorOperatorIndex)); computer.setOperator(operator); String frontSubString = str.substring(0, priorOperatorIndex); Iterator<Character> frontSubStringIterator = getStrIterator(frontSubString); frontSubStringIterator.getEnd(); int leftOperatorNumberIndex = 0; while(frontSubStringIterator.hasPrevious()){ Character previousChar = frontSubStringIterator.getPrevious(); if(previousChar.equals('+') || previousChar.equals('-')){ leftOperatorNumberIndex = frontSubStringIterator.getCurrentIndex()+1; break; } } computer.setLeftOperateNumber(frontSubString.substring(leftOperatorNumberIndex)); String backSubString = str.substring(priorOperatorIndex+1); Iterator<Character> backSubStringIterator = getStrIterator(backSubString); backSubStringIterator.getFirst(); int rightOperatorNumIndex = backSubString.length()-1; while (backSubStringIterator.hasNext()){ Character nextChar = backSubStringIterator.getNext(); if(nextChar.equals('+') || nextChar.equals('-') || nextChar.equals('*') || nextChar.equals('/')){ rightOperatorNumIndex = backSubStringIterator.getCurrentIndex()-1; break; } } computer.setRightOperateNumber(backSubString.substring(0,rightOperatorNumIndex+1)); StringBuilder frontSb = new StringBuilder(str.substring(0,leftOperatorNumberIndex)); String backSb = backSubString.substring(rightOperatorNumIndex+1); str = frontSb.append(computeExpression.interpreter(computer)).append(backSb).toString(); sb = new StringBuffer(str); priorOperatorIndex= indexPriorOperator(sb,priorOperatorList); } Integer nonPriorOperatorIndex = indexPriorOperator(sb,nonPriorOperatorList); // 循环处理含有 +,- 运算符的表达式,直至不包含+,-运算符 while(!nonPriorOperatorIndex.equals(-1)){ computer.setLeftOperateNumber(str.substring(0,nonPriorOperatorIndex)); String operator = String.valueOf(str.charAt(nonPriorOperatorIndex)); computer.setOperator(operator); String backSubStr = str.substring(nonPriorOperatorIndex+1); Iterator<Character> backSubStrIterator = getStrIterator(backSubStr); backSubStrIterator.getFirst(); int rightOperatorNumIndex = backSubStr.length()-1; while (backSubStrIterator.hasNext()){ Character nextChar = backSubStrIterator.getNext(); if(nextChar.equals('+') || nextChar.equals('-')){ rightOperatorNumIndex = backSubStrIterator.getCurrentIndex()-1; break; } } computer.setRightOperateNumber(backSubStr.substring(0,rightOperatorNumIndex+1)); str = computeExpression.interpreter(computer)+(backSubStr.substring(rightOperatorNumIndex+1)); sb = new StringBuffer(str); nonPriorOperatorIndex = indexPriorOperator(sb,nonPriorOperatorList); } return str; }}
复制代码

7.定义计算 Expression 对二元运算进行计算。

public class ComputeExpression implements IExpression{    /**     * 计算 二元运算     * @param computer     * @return     */    @Override    public String interpreter(Computer computer) {        Integer leftInteger = Integer.valueOf(computer.getLeftOperateNumber());        Integer rightInteger = Integer.valueOf(computer.getRightOperateNumber());        String operator = computer.getOperator();        switch (operator){            case "+":                return String.valueOf(leftInteger+rightInteger);            case "-":                return String.valueOf(leftInteger-rightInteger);            case "*":                return String.valueOf(leftInteger*rightInteger);            case "/":                return String.valueOf(leftInteger/rightInteger);            default:                throw new GrammaticalErrorException("表达式含有非法操作服。");        }
}}
复制代码

8.测试代码

public class InterpreterPattern {    public static void main(String[] args) {        Computer computer = new Computer("3*2+(2*(3+4))*3/2+(2*2)-7");        GrammarExpression grammarExpression = new GrammarExpression();        System.out.println(grammarExpression.interpreter(computer));    }}
复制代码

总结:

由测试结果可见:

  1. 解释器模式对于规律性比较强且有严格规定的业务场景很合适。

  2. 解释器模式使用到了递归和循环,执行效率比较低。

发布于: 2022 年 04 月 03 日阅读数: 13
用户头像

下雨了

关注

还未添加个人签名 2021.05.11 加入

还未添加个人简介

评论

发布
暂无评论
用JAVA捋一下设计模式23-解释器模式_设计模式_下雨了_InfoQ写作平台