解释器模式

    解释器模式(Interpreter Pattern)是一种按照规定语法进行解析的方案,在现在项目中使用较少,其定义如下:Given a language, define a representation for its grammar along with aninterpreter that uses the representation to interpret sentences in the language.(给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。)

解释器模式的通用类图:



  • AbstractExpression——抽象解释器  
     具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和NonterminalExpression
    完成。
  • TerminalExpression——终结符表达式
     实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达
    式,但有多个实例,对应不同的终结符。
  • NonterminalExpression——非终结符表达式
     文法中的每条规则对应于一个非终结表达式
  • Context——环境角色
     

   解释器是一个比较少用的模式,以下为其通用源码,可以作为参考。抽象表达式通常只
有一个方法:

public abstract class Expression {
//每个表达式必须有一个解析任务
public abstract Object interpreter(Context ctx);
}
终结符表达式:

public class TerminalExpression extends Expression {
//通常终结符表达式只有一个,但是有多个对象
public Object interpreter(Context ctx) {
return null;
}
}
通常,终结符表达式比较简单,主要是处理场景元素和数据的转换。

非终结符表达式:

public class NonterminalExpression extends Expression {
//每个非终结符表达式都会对其他表达式产生依赖
public NonterminalExpression(Expression... expression){
}
public Object interpreter(Context ctx) {
//进行文法处理
return null;
}
}}

每个非终结符表达式都代表了一个文法规则,并且每个文法规则都只关心自己周边的文
法规则的结果(注意是结果),因此这就产生了每个非终结符表达式调用自己周边的非终结
符表达式,然后最终、最小的文法规则就是终结符表达式,终结符表达式的概念就是如此,
不能够再参与比自己更小的文法运算了。

场景类:

public class Client {
public static void main(String[] args) {
Context ctx = new Context();
//通常定一个语法容器,容纳一个具体的表达式,通常为ListArray、LinkedList、Stack等类型
Stack<Expression> stack = null;
for(;;){
//进行语法判断,并产生递归调用
}
//产生一个完整的语法树,由各个具体的语法分析进行解析
Expression exp = stack.pop();
//具体元素进入场景
exp.interpreter(ctx);
}
}

例子:

业务需求:输入一个模型公式(加、减运算),然后输入模型中的参数,运算出结果。

设计要求:
● 公式可以运行时编辑,并且符合正常算术书写方式,例如a+b-c。
● 高扩展性,未来增加指数、开方、极限、求导等运算符号时较少改动。
● 效率可以不用考虑,晚间批量运算。

需求不复杂,若仅仅对数字采用四则运算,每个程序员都可以写出来。但是增加了增加模型公式就复杂了。先解释一下为什么需要公式,而不采用直接计算的方法,例如有如下3个公式:

● 业务种类1的公式:a+b+c-d。
● 业务种类2的公式:a+b+e-d。
● 业务种类3的公式:a-f。

其中,a、b、c、d、e、f参数的值都可以取得,如果使用直接计算数值的方法需要为每个品种写一个算法,目前仅仅是3个业务种类,那上百个品种呢?歇菜了吧!建立公式,然后通过公式运算才是王道。

在上面公式中,我们可以把他拆分成两个部分 一个数运算符号,一个是运算元素。,运算元素就是指a、b、c等符号,需要具体赋值的对象,也叫做终结符号,为什么叫终结符号呢?因为这些元素除了需要赋值外,不需要做任何处理,所有运算元素都对应一个具体的业务参数,这是语法中最小的单元逻辑,不可再拆分;运算符号就是加减符号,需要我们编写算法进行处理,每个运算符号都要对应处理单元,否则公式无法运行,运算符号也叫做非终结符号。两类元素的共同点是都要被解析,不同点是所有的运算元素具有相同的功能,可以用一个类表示,而运算符号则是需要分别进行解释,加法需要加法解析器,减法需要减法解析器。

所以我们可以设计以下类图:


public abstract class Expression {
	//这里就用hashMap 来指代context环境变量,其中key代表公式中的参数 , value 代表具体数值
	public abstract int interpreter(HashMap<String,Integer> var);
}

public class VarExpression extends Expression{

		private String key;
		
		public VarExpression(String _key){
		this.key = _key;
		}
		//从map中取之
		public int interpreter(HashMap<String, Integer> var) {
		return var.get(this.key);
	}
}

public abstract class SymbolExpression extends Expression {
	protected Expression left;
	protected Expression right;

	// 所有的解析公式都应只关心自己左右两个表达式的结果
	public SymbolExpression(Expression _left, Expression _right) {
		this.left = _left;
		this.right = _right;
	}
}

public class AddExpression extends SymbolExpression {
	public AddExpression(Expression _left, Expression _right) {
		super(_left, _right);
	}

	// 把左右两个表达式运算的结果加起来
	public int interpreter(HashMap<String, Integer> var) {
		return super.left.interpreter(var) + super.right.interpreter(var);
	}
}

public class SubExpression extends SymbolExpression {
	public SubExpression(Expression _left, Expression _right) {
		super(_left, _right);
	}

	// 左右两个表达式相减
	public int interpreter(HashMap<String, Integer> var) {
		return super.left.interpreter(var) - super.right.interpreter(var);
	}
}

public class Calculator {
	// 定义表达式
	private Expression expression;

	// 构造函数传参,并解析
	public Calculator(String expStr) {
		// 定义一个栈,安排运算的先后顺序
		Stack<Expression> stack = new Stack<Expression>();
		// 表达式拆分为字符数组
		char[] charArray = expStr.toCharArray();
		// 运算
		Expression left = null;
		Expression right = null;
		for (int i = 0; i < charArray.length; i++) {
			switch (charArray[i]) {
			case '+': // 加法
				// 加法结果放到栈中
				left = stack.pop();
				right = new VarExpression(String.valueOf(charArray[++i]));
				stack.push(new AddExpression(left, right));
				break;
			case '-':
				left = stack.pop();
				right = new VarExpression(String.valueOf(charArray[++i]));
				stack.push(new SubExpression(left, right));
				break;
			default: // 公式中的变量
				stack.push(new VarExpression(String.valueOf(charArray[i])));
			}
		}
		// 把运算结果抛出来
		this.expression = stack.pop();
	}

	// 开始运算
	public int run(HashMap<String, Integer> var) {
		return this.expression.interpreter(var);
	}
}

public class Client {
	// 运行四则运算
	public static void main(String[] args) throws IOException {
		String expStr = getExpStr();
		// 赋值
		HashMap<String, Integer> var = getValue(expStr);
		Calculator cal = new Calculator(expStr);
		System.out.println("运算结果为:" + expStr + "=" + cal.run(var));
	}

	// 获得表达式
	public static String getExpStr() throws IOException {
		System.out.print("请输入表达式:");
		return (new BufferedReader(new InputStreamReader(System.in))).readLine();
	}

	// 获得值映射
	public static HashMap<String,Integer> getValue(String exprStr) throws IOException{
		HashMap<String,Integer> map = new HashMap<String,Integer>();
		//解析有几个参数要传递
		for(char ch:exprStr.toCharArray()){
			if(ch != '+' && ch != '-'){
				//解决重复参数的问题
				if(!map.containsKey(String.valueOf(ch))){
					String in = (new BufferedReader(new InputStreamReader (System.map.put(String.valueOf(ch),Integer.valueOf(in));
				}
			}
		}
		return map;
	}
}

解释器模式的优点:

解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。

解释器模式的缺点:

● 解释器模式会引起类膨胀
每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
● 解释器模式采用递归调用方法
每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。想想看,如果要排查一个语法错误,我们是不是要一个断点一个断点地调试下去,直到最小的语法单元。
● 效率问题
解释器模式由于使用了大量的循环和递归,效率是一个不容忽视的问题,特别是一用于解析复杂、冗长的语法时,效率是难以忍受的。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值