这个无敌设计,可以解析并运算任意数学表达式

冲天香阵透长安,满城尽带黄金甲。这篇文章主要讲述这个无敌设计,可以解析并运算任意数学表达式相关的知识,希望能为你提供帮助。
1 使用解释器模式解析数学表达式下面用解释器模式来实现一个数学表达式计算器,包含加、减、乘、除运算。
首先定义抽象表达式角色IArithmeticInterpreter接口。

public interface IArithmeticInterpreter { int interpret(); }

创建终结表达式角色Interpreter抽象类。
public abstract class Interpreter implements IArithmeticInterpreter {protected IArithmeticInterpreter left; protected IArithmeticInterpreter right; public Interpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { this.left = left; this.right = right; } }

然后分别创建非终结符表达式角色加、减、乘、除解释器,加法运算表达式AddInterpreter类的代码如下。
public class AddInterpreter extends Interpreter {public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); }public int interpret() { return this.left.interpret() + this.right.interpret(); } }

减法运算表达式SubInterpreter类的代码如下。
public class SubInterpreter extends Interpreter { public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); }public int interpret() { return this.left.interpret() - this.right.interpret(); } }

乘法运算表达式MultiInterpreter类的代码如下。
public class MultiInterpreter extends Interpreter {public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){ super(left,right); }public int interpret() { return this.left.interpret() * this.right.interpret(); }}

除法运算表达式DivInterpreter类的代码如下。
public class DivInterpreter extends Interpreter {public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){ super(left,right); }public int interpret() { return this.left.interpret() / this.right.interpret(); }}

数字表达式NumInterpreter类的代码如下。
public class NumInterpreter implements IArithmeticInterpreter { private int value; public NumInterpreter(int value) { this.value = https://www.songbingjia.com/android/value; }public int interpret() { return this.value; } }

接着创建计算器GPCalculator类。
public class GPCalculator { private Stack< IArithmeticInterpreter> stack = new Stack< IArithmeticInterpreter> (); public GPCalculator(String expression) { this.parse(expression); }private void parse(String expression) { String [] elements = expression.split(" "); IArithmeticInterpreter left,right; for (int i = 0; i < elements.length ; i++) { String operator = elements[i]; if(OperatorUtil.ifOperator(operator)){ left = this.stack.pop(); right = new NumInterpreter(Integer.valueOf(elements[++i])); System.out.println("出栈" + left.interpret() + "和" + right.interpret()); this.stack.push(OperatorUtil.getInterpreter(left,right,operator)); System.out.println("应用运算符:" + operator); }else { NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i])); this.stack.push(numInterpreter); System.out.println("入栈:" + numInterpreter.interpret()); }} }public int calculate() { return this.stack.pop().interpret(); } }

工具类OperatorUtil的代码如下。
public class OperatorUtil {public static boolean isOperator(String symbol) { return (symbol.equals("+") || symbol.equals("-") || symbol.equals("*")); }public static Interpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right, String symbol) { if (symbol.equals("+")) { return new AddInterpreter(left, right); } else if (symbol.equals("-")) { return new SubInterpreter(left, right); } else if (symbol.equals("*")) { return new MultiInterpreter(left, right); } else if (symbol.equals("/")) { return new DivInterpreter(left, right); } return null; } }

最后编写客户端测试代码。
public static void main(String[] args) { System.out.println("result: " + new GPCalculator("10 + 30").calculate()); System.out.println("result: " + new GPCalculator("10 + 30 - 20").calculate()); System.out.println("result: " + new GPCalculator("100 * 2 + 400 * 1 + 66").calculate()); }

运行结果如下图所示。
这个无敌设计,可以解析并运算任意数学表达式

文章图片

当然,上面的简易计算器还没有考虑优先级,就是从左至右依次运算的。在实际运算中,乘法和除法属于一级运算,加法和减法属于二级运算。一级运算需要优先计算。另外,我们可以通过使用括号手动调整运算的优先级。我们再优化一下代码,首先新建一个枚举类。
public enum OperatorEnum { LEFT_BRACKET("("), RIGHT_BRACKET(")"), SUB("-"), ADD("+"), MULTI("*"), DIV("/"), ; private String operator; public String getOperator() { return operator; }OperatorEnum(String operator) { this.operator = operator; } }

然后修改OperatorUtil的处理逻辑,设置两个栈。
public class OperatorUtil {public static Interpreter getInterpreter(Stack< IArithmeticInterpreter> numStack, Stack< String> operatorStack) { IArithmeticInterpreter right = numStack.pop(); IArithmeticInterpreter left = numStack.pop(); String symbol = operatorStack.pop(); System.out.println("数字出栈:" + right.interpret() + "," + left.interpret() + ",操作符出栈:" + symbol); if (symbol.equals("+")) { return new AddInterpreter(left, right); } else if (symbol.equals("-")) { return new SubInterpreter(left, right); } else if (symbol.equals("*")) { return new MultiInterpreter(left, right); } else if (symbol.equals("/")) { return new DivInterpreter(left, right); } return null; } }

【这个无敌设计,可以解析并运算任意数学表达式】修改GPCalculator的代码。
public class GPCalculator {//数字stack private Stack< IArithmeticInterpreter> numStack = new Stack< IArithmeticInterpreter> (); //操作符stack private Stack< String> operatorStack = new Stack< String> (); /** * 解析表达式 * @param expression */ public GPCalculator(String expression) { this.parse(expression); }private void parse(String input) { //对表达式去除空字符操作 String expression = this.fromat(input); System.out.println("标准表达式:" + expression); for (String s : expression.split(" ")) { if (s.length() == 0){ //如果是空格,则继续循环,什么也不操作 continue; } //如果是加减,因为加减的优先级最低,所以这里只要遇到加减号,无论操作符栈中是什么运算符都要运算 else if (s.equals(OperatorEnum.ADD.getOperator()) || s.equals(OperatorEnum.SUB.getOperator())) { //当栈不是空的,并且栈中最上面的一个元素是加减乘除的任意一个 while (!operatorStack.isEmpty() & & (operatorStack.peek().equals(OperatorEnum.SUB.getOperator()) || operatorStack.peek().equals(OperatorEnum.ADD.getOperator()) || operatorStack.peek().equals(OperatorEnum.MULTI.getOperator()) || operatorStack.peek().equals(OperatorEnum.DIV.getOperator()))) { //结果存入栈中 numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack)); } //运算完后将当前的运算符入栈 System.out.println("操作符入栈:"+s); operatorStack.push(s); } //当前运算符是乘除的时候,因为优先级高于加减 //所以要判断最上面的是否是乘除,如果是乘除,则运算,否则直接入栈 else if (s.equals(OperatorEnum.MULTI.getOperator()) || s.equals(OperatorEnum.DIV.getOperator())) { while (!operatorStack.isEmpty()& & ( operatorStack.peek().equals(OperatorEnum.MULTI.getOperator()) || operatorStack.peek().equals(OperatorEnum.DIV.getOperator()))) { numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack)); } //将当前操作符入栈 System.out.println("操作符入栈:"+s); operatorStack.push(s); } //如果是左括号,则直接入栈,什么也不用操作,trim()函数是用来去除空格的,由于上面的分割操作,可能会令操作符带有空格 else if (s.equals(OperatorEnum.LEFT_BRACKET.getOperator())) { System.out.println("操作符入栈:"+s); operatorStack.push(OperatorEnum.LEFT_BRACKET.getOperator()); } //如果是右括号,则清除栈中的运算符直至左括号 else if (s.equals(OperatorEnum.RIGHT_BRACKET.getOperator())) { while (!OperatorEnum.LEFT_BRACKET.getOperator().equals(operatorStack.peek())) { //开始运算 numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack)); } //运算完之后清除左括号 String pop = operatorStack.pop(); System.out.println("括号运算操作完成,清除栈中右括号:"+pop); } //如果是数字,则直接入数据的栈 else { //将数字字符串转换成数字,然后存入栈中 NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(s)); System.out.println("数字入栈:"+s); numStack.push(numInterpreter); } } //最后当栈中不是空的时候继续运算,直到栈为空即可 while (!operatorStack.isEmpty()) { numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack)); } }/** * 计算结果出栈 * @return */ public int calculate() { return this.numStack.pop().interpret(); }/** * 换成标准形式,便于分割 * @param expression * @return */ private String fromat(String expression) { String result = ""; for (int i = 0; i < expression.length(); i++) { if (expression.charAt(i) == ( || expression.charAt(i) == ) || expression.charAt(i) == + || expression.charAt(i) == - || expression.charAt(i) == * || expression.charAt(i) == /) //在操作符与数字之间增加一个空格 result += (" " + expression.charAt(i) + " "); else result += expression.charAt(i); } return result; } }

此时,再来看客户端测试代码。
public static void main(String[] args) { System.out.println("result: " + new GPCalculator("10+30/((6-4)*2-2)").calculate()); }

运行得到预期的结果,如下图所示。
这个无敌设计,可以解析并运算任意数学表达式

文章图片

2 解释器模式在JDK源码中的应用先来看JDK源码中的Pattern对正则表达式的编译和解析。
public final class Pattern implements java.io.Serializable { ... private Pattern(String p, int f) { pattern = p; flags = f; if ((flags & UNICODE_CHARACTER_CLASS) != 0) flags |= UNICODE_CASE; capturingGroupCount = 1; localCount = 0; if (pattern.length() > 0) { compile(); } else { root = new Start(lastAccept); matchRoot = lastAccept; } } ... public static Pattern compile(String regex) { return new Pattern(regex, 0); } public static Pattern compile(String regex, int flags) { return new Pattern(regex, flags); }... }

3 解释器模式在Spring源码中的应用再来看Spring中的ExpressionParser接口。
public interface ExpressionParser {Expression parseExpression(String expressionString) throws ParseException; Expression parseExpression(String expressionString, ParserContext context) throws ParseException; }

这里我们不深入讲解源码,通过我们前面编写的案例大致能够清楚其原理。不妨编写一段客户端代码验证一下。客户端测试代码如下。
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); }

运行结果如下图所示。
这个无敌设计,可以解析并运算任意数学表达式

文章图片

由上图可知,运行结果与预期的结果是一致的。
关注微信公众号『 Tom弹架构 』回复“设计模式”可获取完整源码。

    推荐阅读