共计 8433 个字符,预计需要花费 22 分钟才能阅读完成。
本文节选自《设计模式就该这样学》
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 = 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 弹架构』回复“设计模式”可获取残缺源码。
【举荐】Tom 弹架构:30 个设计模式实在案例(附源码),挑战年薪 60W 不是梦
本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!
如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『Tom 弹架构』可获取更多技术干货!