设计模式 -- 解释器模式(Interpreter Pattern)

1 问题引出

四则运算问题

通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求

  1. 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复

  2. 在分别输入 a ,b, c, d, e 的值

  3. 最后求出结果:

1.2 传统解决

  1. 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果

  2. 问题分析:如果加入新的运算符,比如 * / ( 等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱, 不够清晰.

  3. 解决方案:可以考虑使用解释器模式, 即: 表达式 -> 解释器(可以有多种) -> 结果

2 基本介绍

  1. 解释器模式(Interpreter Pattern)是一种行为设计模式,用于定义一种语言的语法表示,并实现一个解释器来处理该语言中的句子

  2. 解释器模式:是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器, 使用该解释器来解释语言中的句子(表达式)

  3. 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器

3 原理结构图

3.1 类图

3.2 说明

  • 抽象表达式(Abstract Expression):定义了解释器的抽象接口,通常包含一个解释方法,如interpret()。声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享.
  • 终结符表达式(Terminal Expression):实现了抽象表达式的具体类,代表语言中的终结符,如变量或常量。
  • 非终结符表达式(Nonterminal Expression):实现了抽象表达式的具体类,代表语言中的非终结符,如语句或表达式。
  • 上下文(Context):包含解释过程中所需的全局信息,例如变量的值或解释器的状态。
  • 客户端(Client):创建并配置具体的解释器对象,并将需要解释的表达式传递给解释器进行解释。

3.3 作用与原理

语言定义与解析:解释器模式首先定义一个语言的文法规则,包括终结符和非终结符。然后根据这些规则构建语法树,每个节点代表一个文法规则。

解释执行:解释器使用递归方法遍历语法树,从底至顶地解释每个节点,从而实现整个句子的解释执行。上下文对象在这个过程中起到传递全局信息的作用。

4 应用实例

4.1 类图

4.2 代码实现

AddExpression


/**
 * 加法解释器
 * 
 * @author Administrator
 *
 */
public class AddExpression extends SymbolExpression {
​
    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }
​
    //  处理相加
    //  var 仍然是 {a=10,b=20}..
    //  一会我们 debug 源码,就 ok
    public int interpreter(HashMap<String, Integer> var) {
        //  super.left.interpreter(var) : 返回 left 表达式对应的值 a = 10
        //  super.right.interpreter(var): 返回 right 表达式对应值 b = 20 
        return super.left.interpreter(var) + super.right.interpreter(var);
    }
}

Calculator


public class ClientTest {
​
    public static void main(String[] args) throws IOException {
        //  TODO Auto-generated method stub String expStr = getExpStr(); //  a+b
        HashMap<String, Integer> var = getValue(expStr);//  var {a=10, b=20}
​
        Calculator calculator = new Calculator(expStr);
        System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
    }
​
    //  获得表达式
    public static String getExpStr() throws IOException {
        System.out.print("请输入表达式:");
        return (new BufferedReader(new InputStreamReader(System.in))).readLine();
    }
​
    //  获得值映射
    public static HashMap<String, Integer> getValue(String expStr) 
        throws IOException {
        HashMap<String, Integer> map = new HashMap<>();
​
        for (char ch : expStr.toCharArray()) {
            if (ch != '+' && ch != '-') {
                if (!map.containsKey(String.valueOf(ch))) {
                    System.out.print("请输入" + String.valueOf(ch) + "的值:");
                    String in = (new BufferedReader(
                            new InputStreamReader(System.in)))
                                .readLine();
                    map.put(String.valueOf(ch), Integer.valueOf(in));
                }
            }
        }
        return map;
    }
}

Expression

/**
 * 抽象类表达式,通过 HashMap 键值对, 可以获取到变量的值
 */
public abstract class Expression {
    //  a + b - c
    //  解释公式和数值, key 就是公式(表达式) 参数[a,b,c], value 就是就是具体值
    //  HashMap {a=10, b=20}
    public abstract int interpreter(HashMap<String, Integer> var);
}

SubExpression


public class SubExpression extends SymbolExpression {
​
    public SubExpression(Expression left, Expression right) {
​
        super(left, right);
    }
​
    //  求出 left 和 right 表达式相减后的结果
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) - super.right.interpreter(var);
    }
}

SymbolExpression


/**
 * 抽象运算符号解析器 这里,每个运算符号,都只和自己左右两个数字有关系,
 * 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是 Expression 类的实现类
 */
public class SymbolExpression extends Expression {
​
    protected Expression left;
    protected Expression right;
​
    public SymbolExpression(Expression left, Expression right) {
​
        this.left = left;
        this.right = right;
    }
​
    //  因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
    @Override
    public int interpreter(HashMap<String, Integer> var) {
         return 0;
    }
}

VarExpression


/**
 * 变量的解释器
 */
public class VarExpression extends Expression {
​
    private String key; //  key=a,key=b,key=c
​
    public VarExpression(String key) {
        this.key = key;
    }
​
    //  var 就是{a=10, b=20}
    //  interpreter 根据 变量名称,返回对应值
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return var.get(this.key);
    }
}

5 优点与缺点

5.1 优点

扩展性好:通过继承机制容易添加新的解释表达式方式。

灵活性高:可以根据需要轻松扩展或修改文法规则。

简单文法易于实现:对于简单的语言,实现起来相对容易。

5.2 缺点

适用场景有限:仅适用于简单文法的情境,复杂文法会导致维护困难。

执行效率较低:大量使用递归调用,可能导致性能问题。

类膨胀:每条文法规则可能对应一个类,导致系统中类的数量急剧增加。

6 应用场景

6.1 应用场景

        解释器模式适用于将特定类型的问题频繁出现并且可以用简单语言表达的场景。例如,编译器的设计、运算表达式计算、机器人、SQL解析、正则表达式解析等。

6.2 具体实例

编译器:在编译器设计中,源代码需要被解析并翻译成目标代码,解释器模式可以定义编程语言的文法,然后解析源代码。

游戏命令解析:如果游戏中有简单的脚本语言用于控制角色行为,可以使用解释器模式来解析并执行这些脚本命令。

7 总结

        综上所述,解释器模式通过定义语言的文法和构建语法树来实现对语言句子的解释和处理。尽管其在某些情况下可能导致类数量过多和效率问题,但在处理简单文法的语言解释问题时,它提供了一种灵活且可扩展的设计方案。适用于编译器、正则表达式解析等特定场景。

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值