设计模式 ~ 行为型模式 ~ 解释器模式 ~ Interpreter Pattern。

设计模式 ~ 行为型模式 ~ 解释器模式 ~ Interpreter Pattern。



概述。


如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。


    // 用于两个整数相加。
    public static int add(int a, int b) {
        return a + b;
    }

    // 用于两个整数相加。
    public static int add(int a, int b, int c) {
        return a + b + c;
    }

    // 用于 n 个整数相加。
    public static int add(Integer... arr) {
        int sum = 0;
        for (Integer i : arr) {
            sum += i;
        }
        return sum;
    }

上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1 + 2 + 3 + 4 + 5、1 + 2 + 3 - 4 等等。

显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。

定义。

给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和 + - 符号组成的合法序列,“1 + 3 - 2” 就是这种语言的句子。

解释器就是要解析出来语句的含义。但是如何描述规则呢?

文法(语法)规则。

文法是用于描述语言的语法结构的形式规则。

expression ::= value | plus | minus
plus ::= expression '+' expression   
minus ::= expression '-' expression  
value ::= integer

注意:这里的符号 “::=” 表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。

上面规则描述为

表达式可以是一个值,也可以是 plus 或者 minus 运算,而 plus 和 minus 又是由表达式结合运算符构成,值的类型为整型数。

抽象语法树。

在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

用树形来表示符合文法规则的句子。

在这里插入图片描述



结构。

解释器模式包含以下主要角色。

  • 抽象表达式(Abstract Expression)角色。
    定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret();。

  • 终结符表达式(Terminal Expression)角色。
    是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。

  • 非终结符表达式(Nonterminal Expression)角色。
    也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。

  • 环境(Context)角色。
    通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。

  • 客户端(Client)。
    主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。



案例实现。

【eg.】设计实现加减法的软件。

在这里插入图片描述
代码如下。

package com.geek.interpreter.pattern;

/**
 * 抽象表达式类。
 *
 * @author geek
 */
public abstract class AbstractClassExpression {

    /**
     * 解释。
     *
     * @param context
     * @return
     */
    public abstract Integer interpret(Context context);

}

package com.geek.interpreter.pattern;

import java.util.HashMap;
import java.util.Map;

/**
 * 环境类。
 *
 * @author geek
 */
public class Context {

    private final Map<Variable, Integer> map = new HashMap<Variable, Integer>();

    public void assign(Variable variable, Integer value) {
        map.put(variable, value);
    }

    public int getValue(Variable variable) {
        return map.get(variable);
    }

}

package com.geek.interpreter.pattern;

import lombok.Data;
import lombok.ToString;

/**
 * 终结符表达式角色 ~ 变量表达式。
 *
 * @author geek
 */
@Data
@ToString
public class Variable extends AbstractClassExpression {

    /**
     * 声明存储变量名的成员变量。
     */
    private final String name;

    @Override
    public Integer interpret(Context context) {
        // 直接返回变量的值。
        return context.getValue(this);
    }

}

package com.geek.interpreter.pattern;

/**
 * 非终结符表达式角色 ~ 加法表达式。
 *
 * @author geek
 */
public class Plus extends AbstractClassExpression {

    /**
     * 加号左边的表达式。
     */
    private final AbstractClassExpression left;
    /**
     * 加号右边的表达式。
     */
    private final AbstractClassExpression right;

    public Plus(AbstractClassExpression left, AbstractClassExpression right) {
        this.left = left;
        this.right = right;
    }

    /**
     * 解释。
     *
     * @param context
     * @return
     */
    @Override
    public Integer interpret(Context context) {
        return this.left.interpret(context) + this.right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + this.left.toString() + " + " +this.right.toString() + ")";
    }

}

package com.geek.interpreter.pattern;

/**
 * 非终结符表达式角色 ~ 减法表达式。
 *
 * @author geek
 */
public class Minus extends AbstractClassExpression {

    /**
     * 减号左边的表达式。
     */
    private final AbstractClassExpression left;
    /**
     * 减号右边的表达式。
     */
    private final AbstractClassExpression right;

    public Minus(AbstractClassExpression left, AbstractClassExpression right) {
        this.left = left;
        this.right = right;
    }

    /**
     * 解释。
     *
     * @param context
     * @return
     */
    @Override
    public Integer interpret(Context context) {
        return this.left.interpret(context) - this.right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + this.left.toString() + " - " + this.right.toString() + ")";
    }

}

package com.geek.interpreter.pattern;

import lombok.Data;
import lombok.ToString;

/**
 * 终结符表达式角色 ~ 变量表达式。
 *
 * @author geek
 */
@Data
@ToString
public class Variable extends AbstractClassExpression {

    /**
     * 声明存储变量名的成员变量。
     */
    private final String name;

    @Override
    public Integer interpret(Context context) {
        // 直接返回变量的值。
        return context.getValue(this);
    }

}

package com.geek.interpreter.pattern;

import java.util.HashMap;
import java.util.Map;

/**
 * 环境类。
 *
 * @author geek
 */
public class Context {

    /**
     * 存储变量及对应的值。
     */
    private final Map<Variable, Integer> map = new HashMap<Variable, Integer>();

    /**
     * 添加变量。
     *
     * @param variable
     * @param value
     */
    public void assign(Variable variable, Integer value) {
        this.map.put(variable, value);
    }

    /**
     * 根据变量获取对应的值。
     *
     * @param variable
     * @return
     */
    public int getValue(Variable variable) {
        return this.map.get(variable);
    }

}

package com.geek.interpreter.pattern;

/**
 * @author geek
 */
public class Client {

    public static void main(String[] args) {
        // 创建环境对象。
        Context context = new Context();

        // 创建多个变量对象。
        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        Variable d = new Variable("d");
        Variable e = new Variable("e");

        // 将变量存储到环境变量中。
        context.assign(a, 1);
        context.assign(b, 2);
        context.assign(c, 3);
        context.assign(d, 4);
        context.assign(e, 5);

        // 获取抽象语法树。((((a + b) - c) + d) - e)
        AbstractClassExpression expression = new Minus(new Plus(new Minus(new Plus(a, b), c), d), e);
        Integer interpret = expression.interpret(context);
        System.out.println("expression = " + expression);
        System.out.println("interpret = " + interpret);
    }

}

/*
Connected to the target VM, address: '127.0.0.1:61365', transport: 'socket'
((((Variable(name=a) + Variable(name=b)) + Variable(name=c)) + Variable(name=d)) - Variable(name=e))= 5
Disconnected from the target VM, address: '127.0.0.1:61365', transport: 'socket'

Process finished with exit code 0
 */



优缺点。

优点。

  • 易于改变和扩展文法。
    由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

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

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

缺点。

  • 对于复杂文法难以维护。
    在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。

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



使用场景。

  • 当语言的文法较为简单,且执行效率不是关键问题时。

  • 当问题重复出现,且可以用一种简单的语言来进行表达时。

  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lyfGeek

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

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

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

打赏作者

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

抵扣说明:

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

余额充值