用 JAVA 捋一下设计模式 23- 解释器模式
- 2022 年 4 月 03 日
本文字数:7510 字
阅读完需:约 25 分钟
概念及定义
解释器模式在设计模式中利用率是比较低的,且也是比较难以理解的。你可以创造一种新的具有固定语法规则和语义的语言,你可以利用解释器模式来还原这门语言。例如可以将3*2+(2*(3+4))*3/2+(2*2)-7
这行字符串还原成 Java 可以理解的数字运算。
解释器模式中有几个概念:
非终结符:语言中可拆分的元素。
2*(3+4)
即为非终结符。终结符:语言中不可拆分的最小元素。
2
即为终结符。文法:类似于语法,每种语言都有自己的规则。例子中
+-*/
不能连续出现。语句:由终结符构成的符合文法的基本单位。
3*2
即为语句。语法树: 语句的组成结构的树形表示。
解释器模式的主要思想:
工厂类的生产接口以枚举类为参数,在内部根据枚举类生产出对应的产品并返回给调用者。
解释器模式中角色以及职责:
AbstractExpression(抽象表达式)
TerminalExpression,NonterminalExpression 的抽象父类,定义了用于语义解释以及语法解释的公共解释方法。
TerminalExpression(终结符表达式)
用于解释目标语言中的终结符。
NonterminalExpression(非终结符表达式)
用于解释目标语言中的非终结符。
Context(环境类)
环境类定义了一些上下文信息。
Client(客户端)
用来封装解释器的操作。提供统一的对外方法。
解释器模式的优缺点:
优点:
在处理有一定规律且重复性的数据的时候,可以利用解释器模式进行解析。
对文法的修改以及扩展比较方便。
缺点:
一般在解释器中会使用到大量迭代以及递归,执行效率低。
难以理解,利用率较低。
我们在这就利用解释器模式对3*2+(2*(3+4))*3/2+(2*2)-7
这行字符串进行解释,且利用 Java 计算出结果。
实现步骤:
定义一个双向迭代器接口,用于向前或者向后对字符串进行遍历。
/**
* 双向迭代器接口
*/
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));
}
}
总结:
由测试结果可见:
解释器模式对于规律性比较强且有严格规定的业务场景很合适。
解释器模式使用到了递归和循环,执行效率比较低。
版权声明: 本文为 InfoQ 作者【下雨了】的原创文章。
原文链接:【http://xie.infoq.cn/article/8a80cab94dd96e823cdc3e530】。文章转载请联系作者。
下雨了
还未添加个人签名 2021.05.11 加入
还未添加个人简介
评论