1 命令模式概念
1.1 介绍
解释器模式是一种用的比较少的行为型模式,其提供了一种解释语言的语法或表达式的方式。但是它的使用场景确实很广泛,只是因为我们自己很少回去构造一个语言的文法,所以使用较少。
1.2 定义
给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。(其中语言就是我们需要解释的对象,文法就是这个语言的规律,解释器就是翻译机,通过文法来翻译语言。
1.3 使用场景
(1)如果某个简单的语言需要解释执行而且可以将该语言中的语句表示为一个抽象的语法树时可以考虑使用解释器模式。
(2)在某些特定的领域出现不断重复的问题时,可以将该领域的问题转化为一种语法规则下的语句,然后构建解释器来解释该语句。
2 命令模式UML类图通用
(1) AbstractExpression——抽象解释器
具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和Non-terminalExpression完成。
(2)TerminalExpression——终结符表达式
实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。具体到我们例子就是VarExpression类,表达式中的每个终结符都在栈中产生了一个VarExpression对象。
(3) NonterminalExpression——非终结符表达式
文法中的每条规则对应于一个非终结表达式,具体到我们的例子就是加减法规则分别对应到AddExpression和SubExpression两个类。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
(4) Context——环境角色
3 通用模式代码
(1)抽象表达式
public abstract class Expression { // 每个表达式必须有一个解析任务
public abstract Object interpreter(Context ctx);
}
(2)终结符表达式
public class TerminalExpression extends Expression { // 通常终结符表达式只有一个,但是有多个对象
public Object interpreter(Context ctx) {
return null;
}
}
(3)非终结符表达式
public class NonterminalExpression extends Expression {
// 每个非终结符表达式都会对其他表达式产生依赖
public NonterminalExpression(Expression expression) {
}
public Object interpreter(Context ctx) { // 进行文法处理
return null;
}
}
(4)客户类
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);
}
}
4 命令模式简单实现
(1)抽象的算数运算解释器
/**
* 抽象的算数运算解释器
*/
public abstract class ArithemticExpression {
/**
* 抽象的解析方法
* 具体的解析逻辑由具体的子类实现
* @return 解析得到具体的值
*/
public abstract int interpreter();
}
(2)运算符号解释器
/**
* 运算符号解释器
*/
public abstract class OperatorExpression extends ArithemticExpression{
protected ArithemticExpression exp1, exp2;
public OperatorExpression(ArithemticExpression exp1, ArithemticExpression exp2){
this.exp1 = exp1;
this.exp2 = exp2;
}
}
(3)数字解释器
/**
* 数字解释器
*/
public class NumberExpression extends ArithemticExpression{
private int num;
public NumberExpression(int num){
this.num = num;
}
@Override
public int interpreter() {
return num;
}
}
(4)具体的加法运算符解释器
/**
* 具体的加法运算符解释器
*/
public class AdditionExpression extends OperatorExpression{
public AdditionExpression(ArithemticExpression exp1,
ArithemticExpression exp2) {
super(exp1, exp2);
}
@Override
public int interpreter() {
return exp1.interpreter() + exp2.interpreter();
}
}
(5)具体的减法运算符解释器
/**
* 具体的减法运算符解释器
*/
public class SubtractionExpression extends OperatorExpression{
public SubtractionExpression(ArithemticExpression exp1,
ArithemticExpression exp2) {
super(exp1, exp2);
}
@Override
public int interpreter() {
return exp1.interpreter() - exp2.interpreter();
}
}
(6)处理解释器
/**
* 处理解释器
*/
public class Calculator {
// 声明一个Stack栈储存并操作所有相关的解释器
private Stack<ArithemticExpression> mExpStack = new Stack<ArithemticExpression>();
public Calculator(String expression) {
// 声明两个ArithemticExpression类型的临时变量,储存运算符左右两边的数字解释器
ArithemticExpression exp1, exp2;
// 根据空格分割表达式字符串(比如1 + 2 + 3 + 4)
String[] elements = expression.split(" ");
// 遍历表达式元素数组
for (int i = 0; i < elements.length; i++) {
// 判断运算符号
switch (elements[i].charAt(0)) {
case '+':
// 如果是加号,则将栈中的解释器弹出作为运算符号左边的解释器
exp1 = mExpStack.pop();
// 同时将运算符号数组下标的下一个元素构造为一个数字解释器
exp2 = new NumberExpression(Integer.parseInt(elements[++i]));
// 通过上面的两个数字解释器构造加法运算解释器
mExpStack.push(new AdditionExpression(exp1, exp2));
break;
case '-':
exp1 = mExpStack.pop();
exp2 = new NumberExpression(Integer.parseInt(elements[++i]));
mExpStack.push(new SubtractionExpression(exp1, exp2));
break;
default:
// 如果为数字,直接构造数字解释器并压入栈
mExpStack.push(new NumberExpression(Integer
.valueOf(elements[i])));
break;
}
}
}
/**
* 计算结果
* @return 最终的计算结果
*/
public int calculate() {
return mExpStack.pop().interpreter();
}
}
(7)客户端调用
/**
* 客户端调用
*/
public class Client {
public static void main(String[] args) {
Calculator c = new Calculator("2 + 53 + 800 - 501");
System.out.println("计算结果:"+c.calculate());
}
}
(8)结果
5 Android源码中的命令模式
(1)PackageParser
PackageParser是对AndroidManifest.xml配置文件进行读取的,具体原理参考:解析AndroidManifest原理-PackageParser.parserPackage
6 总结
6.1 优点
最大的优点使其灵活的扩展性,当我们想对文法规则进行扩展延伸时,只需要增加相应的非终结符解释器,并在构建抽象语法树时,使用到新增的解释器对象进行具体的解释即可,非常方便。
6.2 缺点
(1)每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
(2)解释器模式由于使用了大量的循环和递归,效率是个问题,特别是用于解析复杂、冗长的语法时,效率是难以忍受的。
7 参考文章与链接
《Android源码设计模式解析与实战》
《设计模式之禅》