做产品的时候,有一个需求:对于一个字符串要在提交之前做校验,但是校验标准需要可配置。最合理的方案就是使用正则表达式+表达式组合。
配置的数据结构如下
package com.example.ayizty.myapplication.reg;
import java.util.HashMap;
public class Configure {
public HashMap<String, String> statements;//variant list
public String expression;//separate all operand by space
public HashMap<String, String> extras;
}
解释器分为:
- 取非:读下一个值,并取非
- 合:对前后两值取和
- 或:对前后两值取或
- 左括号:计算后面所有值,直到遇到右括号
- 右括号:返回前一个值
- 正则表达式:计算值
- 值:布尔值,表示中间结果
expression是由!、&、|、括号和正则表达式组成的逻辑表达式,所有单词用空格分隔开。正则表达式由变量表示,对应放到statements里。(主要是懒得做词法分析)
这是一个标准的解释器模式的应用场景,但是,因为面试题的余孽(逆波兰式,表达式解析什么的),用解释器的模式做解释器不太容易,百度最高的就是用堆栈处理表达式,然后调解释器,这就成了strategy,而不是解释器。
解释器模式的精髓在于使用函数调用的堆栈来遍历表达式树,每个解释器仅对其前后的解释器有感知,对于所有解释器都是调用interpret(为了省事,我这里特殊处理了值解释器)。
package com.example.ayizty.myapplication.reg;
public abstract class AbsOperator {
public static AbsOperator getOperand(String rawOperand, Processor processor) {
switch (rawOperand.charAt(0)) {
case '!':
return new NegativeOperator();
case '&':
return new AndOperator();
case '(':
return new LeftParenthesisOperator();
case '|':
return new OrOperator();
case ')':
return new RightParenthesisOperator();
default:
return new RegOperand(rawOperand, processor);
}
}
public abstract ValueOperand interpret(String input, Processor processor, int index);
}
package com.example.ayizty.myapplication.reg;
public class NegativeOperand extends AbsOperand {
@Override
public ValueOperand interpret(String input, Processor processor, int index) {
processor.removeOperand(index);
AbsOperand next = processor.getOperand(index);
ValueOperand result = next.interpret(input, processor, index);
result.setValue(!result.getValue());
return result;
}
@Override
public String toString() {
return "!";
}
}
package com.example.ayizty.myapplication.reg;
public class AndOperator extends AbsOperator {
@Override
public ValueOperand interpret(String input, Processor processor, int index) {
ValueOperand left = (ValueOperand) processor.removeOperand(index - 1);
processor.removeOperand(index - 1);
AbsOperator next = processor.getOperand(index - 1);
return new ValueOperand(next.interpret(input, processor, index - 1).getValue() && left.getValue());
}
@Override
public String toString() {
return "&&";
}
}
package com.example.ayizty.myapplication.reg;
public class OrOperator extends AbsOperator {
@Override
public ValueOperand interpret(String input, Processor processor, int index) {
ValueOperand left = (ValueOperand) processor.removeOperand(index - 1);
processor.removeOperand(index - 1);
AbsOperator next = processor.getOperand(index - 1);
return new ValueOperand(next.interpret(input, processor, index - 1).getValue() || left.getValue());
}
@Override
public String toString() {
return "||";
}
}
package com.example.ayizty.myapplication.reg;
public class LeftParenthesisOperator extends AbsOperator {
@Override
public ValueOperand interpret(String input, Processor processor, int index) {
processor.removeOperand(index);
AbsOperator expected = null;
do {
AbsOperator next = processor.getOperand(index);
processor.putOperand(index, next.interpret(input, processor, index));
expected = processor.getOperand(index + 1);
} while (!(expected instanceof RightParenthesisOperator));
return expected.interpret(input, processor, index + 1);
}
@Override
public String toString() {
return "(";
}
}
public class RightParenthesisOperator extends AbsOperator {
@Override
public ValueOperand interpret(String input, Processor processor, int index) {
ValueOperand operand = (ValueOperand) processor.removeOperand(index - 1);
processor.removeOperand(index - 1);
return operand;
}
@Override
public String toString() {
return ")";
}
}
public class RegOperand extends AbsOperator {
private Pattern mPattern;
public RegOperand(String rawOperand, Processor processor) {
mPattern = Pattern.compile(processor.getRawStatement(rawOperand));
}
@Override
public ValueOperand interpret(String input, Processor processor, int index) {
processor.removeOperand(index);
Matcher matcher = mPattern.matcher(input);
ValueOperand result = new ValueOperand(matcher.find());
System.out.println(index + " " + this + " 3 " + result);
return result;
}
@Override
public String toString() {
return mPattern.toString();
}
}
public class ValueOperand extends AbsOperator {
private boolean mValue;
public ValueOperand(boolean value) {
mValue = value;
}
@Override
public ValueOperand interpret(String input, Processor processor, int index) {
AbsOperator next = processor.getOperand(index + 1);
return next.interpret(input, processor, index + 1);
}
public void setValue(boolean value) {
mValue = value;
}
public boolean getValue() {
return mValue;
}
@Override
public String toString() {
return String.valueOf(mValue);
}
}
值之所以有这个intercept是因为整体流程的需要。主要就是Processor的结构:
package com.example.ayizty.myapplication.reg;
import java.util.ArrayList;
public class Processor {
private Configure mConfigure;
private ArrayList<AbsOperator> mOperands;
public Processor(Configure configure) {
mConfigure = configure;
String expression = configure.expression;
String[] rawOperands = expression.split(" ");
final int length = rawOperands.length;
mOperands = new ArrayList<>();
for (int i = 0; i < length; ++i) {
mOperands.add(AbsOperator.getOperand(rawOperands[i], this));
}
System.out.println("init " + this);
}
public AbsOperator removeOperand(int index) {
return mOperands.remove(index);
}
public AbsOperator getOperand(int index) {
return mOperands.get(index);
}
public void putOperand(int i, AbsOperator operand) {
mOperands.add(i, operand);
}
public String getRawStatement(String name) {
return mConfigure.statements.get(name);
}
public boolean process(String input) {
while (1 != mOperands.size()) {
ValueOperand operand = mOperands.get(0).interpret(input, this, 0);
mOperands.add(0, operand);
System.out.println("processor " + this);
}
AbsOperator operand = mOperands.get(0);
if (operand instanceof ValueOperand) {
return ((ValueOperand) operand).getValue();
}
return false;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
final int length = mOperands.size();
for (int i = 0; i < length; ++i) {
stringBuilder.append(mOperands.get(i));
}
return stringBuilder.toString();
}
}
这里就是解释器模式的整体调用逻辑,没有树、没有堆栈,简单到底的对第一个解释器进行解释。
解释器模式确实让代码简单的令人发指,但是有一些问题:
- 类爆炸,简简单单的逻辑表达式已经10个类了,而且每个类都非常简单
- 过多的对象,每次运行都会产生大量中间变量,固然是很不好的。可以使用对象池+工厂解决。