设计模式——解释器模式

解释器模式

解释器模式(Interpreter Pattern):给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少

解释器模式中的角色:

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

抽象表达式Expression:

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

终结符表达式TerminalExpression:

public class TerminalExpression extends Expression { 
    //通常终结符表达式只有一个,但是有多个对象 
    public Object interpreter(Context ctx) { 
        return null; 
    } 
}

非终结符表达式NonterminalExpression:

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

客户类Client:

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); 
    } 
}

通常Client是一个封装类,封装的结果就是传递进来一个规范语法文件解析器分析后产生结果并返回,避免了调用者与语法解析器的耦合关系。


解释器模式的优点

扩展性:修改语法规则,只要修改相应的非终结符表达式扩展语法,则只要增加非终结符类

解释器模式的缺点

  • 解释器模式会引起类膨胀:每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
  • 解释器模式采用递归调用方法:每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。
  • 效率问题:解释器模式使用了大量的循环和递归,当解析复杂、冗长的语法时,效率是难以忍受的。

解释器模式使用的场景

  • 重复发生的问题可以使用解释器模式
  • 一个简单语法需要解释的场景

解释器模式的注意事项

尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。


解释器模式的实例

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

设计要求:

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

(1)抽象表达式类

import java.util.HashMap;
public abstract class Expression {
	//解析公式和数值,其中var中的key值是公式中的参数,value值是具体的数字
	public abstract int interpreter(HashMap<String, Integer> var);
}

(2)变量解析器

import java.util.HashMap;
import com.sfq.impl.Expression;
public class VarExpression extends Expression {
	private String key;
	public VarExpression(String key) {
		this.key = key;
	}
	//从map中取之
	@Override
	public int interpreter(HashMap<String, Integer> var) {		
		return var.get(this.key);
	}
}

(3)抽象运算符号解析器

public abstract class SymbolExpression extends Expression {
	protected Expression left;
	protected Expression right;	
	public SymbolExpression(Expression left, Expression right) {
		this.left = left;
		this.right = right;
	}
}

(4)加法解析器

import java.util.HashMap;
import com.sfq.impl.Expression;
import com.sfq.impl.SymbolExpression;
public class AddExpression extends SymbolExpression {
	public AddExpression(Expression left, Expression right) {
		super(left, right);
	}
	//把左右两个表达式运算的结果加起来
	@Override
	public int interpreter(HashMap<String, Integer> var) {
		return super.left.interpreter(var) + super.right.interpreter(var);
	}
}

(5)减法解析器

import java.util.HashMap;
import com.sfq.impl.Expression;
import com.sfq.impl.SymbolExpression;
public class SubExpression extends SymbolExpression {
	public SubExpression(Expression left, Expression right) {
		super(left, right);
	}
	@Override
	public int interpreter(HashMap<String, Integer> var) {		
		return super.left.interpreter(var) - super.right.interpreter(var);
	}
}

(6)解析器封装类

import java.util.HashMap;
import java.util.Stack;
import com.sfq.impl.Expression;
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])));
				break;
			}
		}
		//抛出运算结果
		this.expression = stack.pop();
	}
	//开始运算
	public int run(HashMap<String, Integer> var) {
		return this.expression.interpreter(var);
	}
}

(7)客户模拟类

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import com.sfq.action.Calculator;
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.println("请输入表达式:");
		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.in))).readLine();
					map.put(String.valueOf(ch), Integer.valueOf(in));
				}
			}
		}
		return map;
	}
}

结果
请输入表达式:
a+b-c
100
20
40
运算结果为:a+b-c=80

公式是在运行时定义的,而不是在运行前就制定好的,是不是类似于初中学过的代数”这门课?先公式,然后赋值,运算出结果

若需要扩展也非常容易,只要增加SymbolExpression的子类就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥羊汤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值