17、解释器模式(Interpreter Pattern)

1. 解释器模式

1.1 简介

  Interpreter(解释器)模式是对特定的计算机程序设计语言,用来解释预先定义的文法。Interpreter模式是一种简单的语法解释器,属于行为模式。给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

  编译原理上的编译器分为词法分析器、语法分析器、语义分析器、中间代码优化器以及最终的最终代码生成器几个部分。而这个解释器其实就是完成了对语法的解析,将一个个的词组解释成了一个个语法范畴,之后拿来使用。Interpreter模式的目的就是使用一个解释器为用户提供一个一门定义语言的语法表示的解释器,然后通过这个解释器来解释语言中的句子。提供了一个实现语法解释器的框架。

  Interpreter(解释器)模式大多用来解释一些(自定义的)独特语法,例如某些游戏开发引擎中读取XML文件,或是WindowsPhone开发中的XAML文件,都是使用此模式来进行的。与其说是一种模式,不如说是一种具有通用规范的行为更为准确。

1.2 结构

Interpreter 模式uml:

Interpreter 模式uml.png

Interpreter 模式角色:

  • AbstractInterpreter 定义interpret操作,声明一个所有的具体表达式都需要实现的抽象接口;这个接口主要是一个interpret()方法,称做解释操作。

  • TerminalInterpreter 终态节点,实现了抽象表达式所要求的接口;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如公式R=R1+R2,R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。

  • NonTerminalInterpreter 非终态节点,内部调用其他的Interpreter。文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,“+"就是非终结符,解析“+”的解释器就是一个非终结符表达式。

  • Context 上下文对象,存放所有Interpreter共享的信息。一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,给R1赋值100,给R2赋值200,这些信息需要存放到环境中。

2. Interpreter 模式示例

  Interpreter 模式的示例代码很简单, 只是为了说明模式的组织和使用, 实际的解释Interpret 逻辑没有实际提供。

  Interpreter模式和Composite模式相似,最终将构造为一颗语法树。以算术表达式"20(3+1)-45+3"为例,用Interpreter 模式写一个计算表达式的示例。

Interpreter接口:

public interface Interpreter {
    double calculate(String expression);
}

终态Interpreter:

public class Number implements Interpreter {

    private double number;

    public Number(double number) {
        super();
        this.number = number;
    }

    @Override
    public double calculate(String expression) {
        return number;
    }

}

非终态加法Interpreter:

public class Add implements Interpreter {

    private Interpreter left;

    private Interpreter right;

    public Add(Interpreter left, Interpreter right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public double calculate(String expression) {
        return left.calculate(expression) + right.calculate(expression);
    }

}

解释器ParserInterpreter(解析算术表达式,构造语法树):

public class ParserInterpreter implements Interpreter {

    @Override
    public double calculate(String expression) { //1 * (2 + 3)
        StringBuilder number = new StringBuilder();
        LinkedList<Interpreter> interpreters = new LinkedList<>();
        LinkedList<Character> operators = new LinkedList<>();
        for (char ch : expression.toCharArray()) {
            if (isOperator(ch)) {
                //将之前的数字入栈
                if (number.length() > 0) {
                    interpreters.add(new Number(Double.parseDouble(number.toString())));
                    number.setLength(0);
                }
                //组装表达式
                while (interpreters.size() >= 2) {
                    Character lastOp = operators.getLast();
                    //碰到左括号
                    if (isOpenParen(lastOp)) {
                        break;
                    }
                    //碰到了运算符,但下一个运算符优先级是否更高?
                    if (rightOperatorGreater(lastOp, ch)) {
                        break;
                    }
                    Interpreter right = interpreters.removeLast();
                    Interpreter left = interpreters.removeLast();
                    Interpreter interpreter = constructExpression(left, 
                            operators.removeLast(), right);
                    interpreters.addLast(interpreter);
                }
                if (isCloseParen(ch)) {
                    //碰到右括号,直接去掉左括号
                    operators.removeLast();
                } else {
                    //非右括号,直接进栈
                    operators.addLast(ch);
                }
            } else {
                number.append(ch);
            }
        }
        //最后是数字,如1*2+3
        if (number.length() > 0) {
            interpreters.add(new Number(Double.parseDouble(number.toString())));
            number.setLength(0);
        }
        //最后一次运算
        if (operators.size() > 0) {
            Interpreter right = interpreters.removeLast();
            Interpreter left = interpreters.removeLast();
            Interpreter interpreter = constructExpression(left, 
                    operators.removeLast(), right);
            interpreters.addLast(interpreter);
        }
        //调用组装好的树
        return interpreters.pop().calculate(expression);
    }
    
    /**
     * 右边运算符是否优先级更高
     */
    private boolean rightOperatorGreater(char leftOp, char rightOp) {
        if (rightOp == '*' || rightOp == '/') {
            return leftOp == '+' || leftOp == '-';
        }
        return false;
    }
    
    private boolean isOperator(char ch) {
        return ch == '-' || ch == '+' || ch == '/' || ch == '*' || ch == '(' || ch ==')';
    }
    
    private boolean isOpenParen(char ch) {
        return ch == '(';
    }
    
    private boolean isCloseParen(char ch) {
        return ch == ')';
    }
    
    private Interpreter constructExpression(Interpreter left, char op, Interpreter right) {
        switch (op) {
        case '+' :
            return new Add(left, right);
        case '-' :
            return new Sub(left, right);
        case '*' :
            return new Plus(left, right);
        case '/' :
            return new Divide(left, right);
        default:
            break;
        }
        return null;
    }

}

调用示例:

    public static void main(String[] args) {
        ParserInterpreter interpreter = new ParserInterpreter();
        double result = interpreter.calculate("20*(3+1)-4*5+3");
        System.out.println("计算结果为: " + result);
    }

3. 总结

Interpreter 模式优点:

  • 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。

  • 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

  • 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。

  • 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。

Interpreter 模式缺点:

  • 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。

  • 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342