解释器模式

简介

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

解释器模式(Interpreter Pattern)是一种按照规定的语法(文法)进行解析的模式。

就比如编译器可以将源码编译解释为机器码,让 CPU 能进行识别并运行。解释器模式 的作用其实与编译器一样,都是将一些固定的文法(即语法)进行解释,构建出一个解释句子的解释器。

简单理解,解释器是一个简单语法分析工具,它可以识别句子语义,分离终结符号和非终结符号,提取出需要的信息,让我们能针对不同的信息做出相应的处理。

解释器模式 核心:识别文法,构建解释

主要解决

如果存在一种特定类型的问题,该类型问题涉及多个不同实例,但是具备固定文法描述,那么可以使用 解释器模式 对该类型问题进行解释,分离出需要的信息,根据获取的信息做出相应的处理。

简而言之,对于一些固定文法构建一个解释句子的解释器。

优缺点

优点

  • 扩展性强:在 解释器模式 中由于语法是由很多类表示的,当语法规则更改时,只需修改相应的非终结符表达式即可;若扩展语法时,只需添加相应非终结符类即可;
  • 增加了新的解释表达式的方式;
  • 易于实现文法:解释器模式 对应的文法应当是比较简单且易于实现的,过于复杂的语法并不适合使用 解释器模式

缺点

  • 语法规则较复杂时,会引起类膨胀:解释器模式 每个语法都要产生一个非终结符表达式,当语法规则比较复杂时,就会产生大量的解释类,增加系统维护困难;
  • 执行效率比较低:解释器模式 采用递归调用方法,每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,因此完整表达式的最终结果是通过从后往前递归调用的方式获取得到。当完整表达式层级较深时,解释效率下降,且出错时调试困难,因为递归迭代层级太深;

使用场景

  • 一些重复出现的问题可以用一种简单的语言来进行表达;
  • 一个简单语法需要解释的场景;

模式讲解

首先来看下 解释器模式 的通用 UML 类图:

解释器模式

从 UML 类图中,我们可以看到,解释器模式 主要包含四种角色:

  • 抽象表达式(Expression):负责定义一个解释方法interpret,交由具体子类进行具体解释;
  • 终结符表达式(TerminalExpression):实现文法中与终结符有关的解释操作。文法中的每一个终结符都有一个具体终结表达式与之相对应,比如公式 R=R1+R2,R1 和 R2 就是终结符,对应的解析 R1 和 R2 的解释器就是终结符表达式。通常一个 解释器模式 中只有一个终结符表达式,但有多个实例,对应不同的终结符(R1,R2);
  • 非终结符表达式(NonterminalExpression):实现文法中与非终结符有关的解释操作。文法中的每条规则都对应于一个非终结符表达式。非终结符表达式一般是文法中的运算符或者其他关键字,比如公式 R=R1+R2 中,“+"就是非终结符,解析“+”的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式;
  • 上下文环境类(Context):包含解释器之外的全局信息。它的任务一般是用来存放文法中各个终结符所对应的具体值,比如 R=R1+R2,给 R1 赋值100,给 R2 赋值200,这些信息需要存放到环境中;

以下是 解释器模式 的通用代码:

class Client {
    public static void main(String[] args) {
        Context context = new Context();
        // 定义一个语法容器,用于存储一个具体表达式
        Stack<IExpression> stack = new Stack<>();
        for (;;) {
            // 进行语法解析,并产生递归调用
        }
        // 获取得到最终的解析表达式:完整语法树
        IExpression expression = stack.pop();
        // 递归调用获取结果
        expression.interpret(context);

    }

    // 上下文环境类
    static class Context extends HashMap {

    }

    // 抽象表达式
    interface IExpression {
        // 对表达式进行解释
        Object interpret(Context context);
    }

    // 终结符表达式
    static class TerminalExpression implements IExpression {

        @Override
        public Object interpret(Context context) {
            // 实现文法中与终结符有关的操作
            return null;
        }

    }

    // 非终结符表达式
    static class NonterminalExpression implements IExpression {
        public NonterminalExpression(IExpression... expression) {
            // 每个非终结符表达式都会对其他表达式产生依赖
        }

        @Override
        public Object interpret(Context context) {
            // 进行文法处理
            return null;
        }
    }
}

举个例子

例子:请用代码实现表达式求值:a + b - c...

分析:表达式 a + b - c...,我们把整个表达式看成是一个固定格式的文法,其中,a b c 是终结符,+ - 是非终结符。

具体代码如下:

class Client {
    public static void main(String[] args) {
        System.out.println("result: " + new Calculator("10 + 30").calculate());
        System.out.println("result: " + new Calculator("10 + 30 - 20").calculate());
        System.out.println("result: " + new Calculator("10 + 30 - 20 + 15").calculate());
    }

    interface IArithmeticExpression {
        int interpret();
    }

    static class NumExpression implements IArithmeticExpression {
        private int value;

        public NumExpression(int value) {
            this.value = value;
        }

        @Override
        public int interpret() {
            return this.value;
        }
    }

    static abstract class OperatorExpression implements IArithmeticExpression {

        protected IArithmeticExpression left;
        protected IArithmeticExpression right;

        public OperatorExpression(IArithmeticExpression left, IArithmeticExpression right) {
            this.left = left;
            this.right = right;
        }
    }

    static class AddExpression extends OperatorExpression {
        public AddExpression(IArithmeticExpression left, IArithmeticExpression right) {
            super(left, right);
        }

        @Override
        public int interpret() {
            return this.left.interpret() + this.right.interpret();
        }
    }

    static class SubtractionExpression extends OperatorExpression {
        public SubtractionExpression(IArithmeticExpression left, IArithmeticExpression right) {
            super(left, right);
        }

        @Override
        public int interpret() {
            return this.left.interpret() - this.right.interpret();
        }
    }

    static class Calculator {
        private Stack<IArithmeticExpression> stack = new Stack<>();

        public Calculator(String expression) {
            this.analyse(expression);
        }

        private void analyse(String expression) {
            String[] elements = expression.split(" ");
            IArithmeticExpression leftExpr, rightExpr;
            for (int i = 0, len = elements.length; i < len; ++i) {
                switch (elements[i]) {
                case "+":
                    leftExpr = this.stack.pop();
                    rightExpr = new NumExpression(Integer.valueOf(elements[++i]));
                    this.stack.push(new AddExpression(leftExpr, rightExpr));
                    break;
                case "-":
                    leftExpr = this.stack.pop();
                    rightExpr = new NumExpression(Integer.valueOf(elements[++i]));
                    this.stack.push(new SubtractionExpression(leftExpr, rightExpr));
                    break;
                default:
                    this.stack.push(new NumExpression(Integer.valueOf(elements[i])));
                    break;
                }
            }
        }

        public int calculate() {
            return this.stack.pop().interpret();
        }
    }
}

:这里为了方便,我们直接传入具体表达式(表达式格式要求每个元素之间使用空格隔开),省略了使用 Context 存储额外信息。

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

推荐阅读更多精彩内容