这个无敌设计,可以解析并运算任意数学表达式
时间:2021-12-04 作者:gupaoedu-tom
本文节选自《设计模式就该这样学》
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) {
域名 = left;
域名t = right;
}
}
然后分别创建非终结符表达式角色加、减、乘、除解释器,加法运算表达式AddInterpreter类的代码如下。
public class AddInterpreter extends Interpreter {
public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
super(left, right);
}
public int interpret() {
return 域名rpret() + 域名rpret();
}
}
减法运算表达式SubInterpreter类的代码如下。
public class SubInterpreter extends Interpreter {
public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
super(left, right);
}
public int interpret() {
return 域名rpret() - 域名rpret();
}
}
乘法运算表达式MultiInterpreter类的代码如下。
public class MultiInterpreter extends Interpreter {
public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
super(left,right);
}
public int interpret() {
return 域名rpret() * 域名rpret();
}
}
除法运算表达式DivInterpreter类的代码如下。
public class DivInterpreter extends Interpreter {
public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
super(left,right);
}
public int interpret() {
return 域名rpret() / 域名rpret();
}
}
数字表达式NumInterpreter类的代码如下。
public class NumInterpreter implements IArithmeticInterpreter {
private int value;
public NumInterpreter(int value) {
域名e = value;
}
public int interpret() {
return 域名e;
}
}
接着创建计算器GPCalculator类。
public class GPCalculator {
private Stack<IArithmeticInterpreter> stack = new Stack<IArithmeticInterpreter>();
public GPCalculator(String expression) {
域名e(expression);
}
private void parse(String expression) {
String [] elements = 域名t(" ");
IArithmeticInterpreter left,right;
for (int i = 0; i < 域名th ; i++) {
String operator = elements[i];
if(域名erator(operator)){
left = 域名();
right = new NumInterpreter(域名eOf(elements[++i]));
域名tln("出栈" + 域名rpret() + "和" + 域名rpret());
域名(域名nterpreter(left,right,operator));
域名tln("应用运算符:" + operator);
}else {
NumInterpreter numInterpreter = new NumInterpreter(域名eOf(elements[i]));
域名(numInterpreter);
域名tln("入栈:" + 域名rpret());
}
}
}
public int calculate() {
return 域名().interpret();
}
}
工具类OperatorUtil的代码如下。
public class OperatorUtil {
public static boolean isOperator(String symbol) {
return (域名ls("+") || 域名ls("-") || 域名ls("*"));
}
public static Interpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter
right, String symbol) {
if (域名ls("+")) {
return new AddInterpreter(left, right);
} else if (域名ls("-")) {
return new SubInterpreter(left, right);
} else if (域名ls("*")) {
return new MultiInterpreter(left, right);
} else if (域名ls("/")) {
return new DivInterpreter(left, right);
}
return null;
}
}
最后编写客户端测试代码。
public static void main(String[] args) {
域名tln("result: " + new GPCalculator("10 + 30").calculate());
域名tln("result: " + new GPCalculator("10 + 30 - 20").calculate());
域名tln("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) {
域名ator = operator;
}
}
然后修改OperatorUtil的处理逻辑,设置两个栈。
public class OperatorUtil {
public static Interpreter getInterpreter(Stack<IArithmeticInterpreter> numStack, Stack<String> operatorStack) {
IArithmeticInterpreter right = 域名();
IArithmeticInterpreter left = 域名();
String symbol = 域名();
域名tln("数字出栈:" + 域名rpret() + "," + 域名rpret() + ",操作符出栈:" + symbol);
if (域名ls("+")) {
return new AddInterpreter(left, right);
} else if (域名ls("-")) {
return new SubInterpreter(left, right);
} else if (域名ls("*")) {
return new MultiInterpreter(left, right);
} else if (域名ls("/")) {
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) {
域名e(expression);
}
private void parse(String input) {
//对表达式去除空字符操作
String expression = 域名at(input);
域名tln("标准表达式:" + expression);
for (String s : 域名t(" ")) {
if (域名th() == 0){
//如果是空格,则继续循环,什么也不操作
continue;
}
//如果是加减,因为加减的优先级最低,所以这里只要遇到加减号,无论操作符栈中是什么运算符都要运算
else if (域名ls(域名perator())
|| 域名ls(域名perator())) {
//当栈不是空的,并且栈中最上面的一个元素是加减乘除的任意一个
while (!域名pty()
&&(域名().equals(域名perator())
|| 域名().equals(域名perator())
|| 域名().equals(域名perator())
|| 域名().equals(域名perator()))) {
//结果存入栈中
域名(域名nterpreter(numStack,operatorStack));
}
//运算完后将当前的运算符入栈
域名tln("操作符入栈:"+s);
域名(s);
}
//当前运算符是乘除的时候,因为优先级高于加减
//所以要判断最上面的是否是乘除,如果是乘除,则运算,否则直接入栈
else if (域名ls(域名perator())
|| 域名ls(域名perator())) {
while (!域名pty()&&(
域名().equals(域名perator())
|| 域名().equals(域名perator()))) {
域名(域名nterpreter(numStack,operatorStack));
}
//将当前操作符入栈
域名tln("操作符入栈:"+s);
域名(s);
}
//如果是左括号,则直接入栈,什么也不用操作,trim()函数是用来去除空格的,由于上面的分割 操作,可能会令操作符带有空格
else if (域名ls(域名perator())) {
域名tln("操作符入栈:"+s);
域名(域名perator());
}
//如果是右括号,则清除栈中的运算符直至左括号
else if (域名ls(域名perator())) {
while (!域名perator().equals(域名())) {
//开始运算
域名(域名nterpreter(numStack,operatorStack));
}
//运算完之后清除左括号
String pop = 域名();
域名tln("括号运算操作完成,清除栈中右括号:"+pop);
}
//如果是数字,则直接入数据的栈
else {
//将数字字符串转换成数字,然后存入栈中
NumInterpreter numInterpreter = new NumInterpreter(域名eOf(s));
域名tln("数字入栈:"+s);
域名(numInterpreter);
}
}
//最后当栈中不是空的时候继续运算,直到栈为空即可
while (!域名pty()) {
域名(域名nterpreter(numStack,operatorStack));
}
}
/**
* 计算结果出栈
* @return
*/
public int calculate() {
return 域名().interpret();
}
/**
* 换成标准形式,便于分割
* @param expression
* @return
*/
private String fromat(String expression) {
String result = "";
for (int i = 0; i < 域名th(); i++) {
if (域名At(i) == \'(\' || 域名At(i) == \')\' ||
域名At(i) == \'+\' || 域名At(i) == \'-\' ||
域名At(i) == \'*\' || 域名At(i) == \'/\')
//在操作符与数字之间增加一个空格
result += (" " + 域名At(i) + " ");
else
result += 域名At(i);
}
return result;
}
}
此时,再来看客户端测试代码。
public static void main(String[] args) {
域名tln("result: " + new GPCalculator("10+30/((6-4)*2-2)").calculate());
}
运行得到预期的结果,如下图所示。
2 解释器模式在JDK源码中的应用
先来看JDK源码中的Pattern对正则表达式的编译和解析。
public final class Pattern implements 域名alizable {
...
private Pattern(String p, int f) {
pattern = p;
flags = f;
if ((flags & UNICODE_CHARACTER_CLASS) != 0)
flags |= UNICODE_CASE;
capturingGroupCount = 1;
localCount = 0;
if (域名th() > 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 = 域名eExpression("100 * 2 + 400 * 1 + 66");
int result = (Integer) 域名alue();
域名tln("计算结果是:" + result);
}
```
运行结果如下图所示。
![file](https://域名/other/2593013/202111/2593013-20211118150609078-域名)
由上图可知,运行结果与预期的结果是一致的。
**关注微信公众号『 Tom弹架构 』回复“设计模式”可获取完整源码。**
> [【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦](https://域名/gupaoedu-tom/p/域名)
> 本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!