8.1解释器模式 (Interpreter Pattern)

一. 定义

在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性,如果将它们归纳成一种简单的语言,则这些问题实例将是该语言的一些句子,这样就可用"编译原理"中的解释器模式来实现了;

解释器模式(Interpreter Pattern)

1.是一种行为型模式,用以给分析对象定义一个语言,并定义该语言的文法表示,在设计一个解释器来解释语言中的句子(表达式);

2.这里文法 指 语言的语法规则, 而 句子 是语言集中的元素, 可用一颗语法树来直观地描述语言中的句子;

3.在编译原理中,一个算术表达式通过 词法分析器 形成词法单元,再通过 语法分析器 构建 语法分析树,这两个分析器都是解释器;

二. 特点

1. 优点

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

  2. 容易实现,在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易;

2. 缺点

  1. 执行效率低,解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度会很慢,且代码调试麻烦;

  2. 会引起类的膨胀,解释器模式中的每条规则至少需要定义一个类,当包含的文法规则较多时,类的个数会激增,系统将难以维护;

  3. 可应用的场景比较少,在软件开发中,需要定义语言文法的应用实例非常少,故这种模式很少被使用;

三. 应用场景

1.当问题重复出现,且可用一种简单的语言进行表达时,如编译器,运算表达时计算,正则表达式;

2.当一个语言需要解释执行,且的语言中的句子可表示为一个抽象语法树时,如 XML文档解释;

3.解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题;

四. 模式的结构

1. 解释器模式结构图

2. 解释器模式角色及职责

1)AbstractExpression(抽象表达式): 声明一个抽象的解释方法interpret(), 这个方法为抽象语法树中所有节点所共享;

2)TerminalExpression(终结符表达式类): 实现文法中与终结符相关的解释操作,文法中每个终结符都对应一个具体终结表达式;

3)NonterminalExpression(非终结表达式类): 实现文法中与非终结符相关的解释操作,每条规则都对应一个非终结符表达式;

4)Context(环境类): 含有解释器之外的全局信息,一般用来传递被所有解释器共享的数据,后面的解释器可从这里获取这些值;

5)Client(客户端): 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,再调用Context的解释方法;

6)说明: 输入 Context 和 TerminalExpression 信息通过Client 输入即可

五. 模式的实现

0.实现加减运算

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

2) 分别输入 a、b、c、d、e的值;

3) 最后要求输出结果: 如图

1.传统方案解决

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

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

2.解释器模式实现

表达式 -> 解释器(可以有多种) ->结果

1> 实例结构图

2> 相关代码实现

//解释器模式 客户端
object InterpreterClient {
    @JvmStatic
    fun main(args: Array<String>) {
        //先输入的运算表达式
        val expStr = getExpStr() // a+b-c
        //表达式存入map集合中
        val value: HashMap<String, Int> = toMap(expStr) // var {a = 10, b=20, c=5}
        //使用计算器
        val calculator = Calculator(expStr)
        //得到计算结果
        println("运算结果: $expStr = ${calculator.run(value)}")
    }


    //获得要运算的表达式
    private fun getExpStr(): String {
        print("请输入运算的表达式: ")
        return (BufferedReader(InputStreamReader(System.`in`))).readLine()
    }


    //将表达式存入map集合
    private fun toMap(expStr: String): HashMap<String, Int> {
        val map: HashMap<String, Int> = HashMap()
        for (ch in expStr.toCharArray()) {
            if (ch != '+' && ch != '-') {
                val key = ch.toString()
                if (!map.containsKey(key)) {
                    print("请输入: $key 的值: ")
                    //为单个变量赋值
                    val inStr = (BufferedReader(InputStreamReader(System.`in`))).readLine()
                    map[key] = inStr.toInt()
                }
            }
        }
        return map
    }
}


//抽象表达式,通过HashMap 键值对, 可以获取到变量的值
abstract class Expression {
    /**
     * 解释公式的键和值
     * key 就是公式(表达式) 参数[a,b],
     * value 就是具体值 10, 20
     * HashMap {a=10, b=20}
     */
    abstract fun interpret(exp:HashMap<String,Int>):Int
}
/**
 * 抽象运算符号解析器 - 非终结表达式
 * 这里每个运算符号两边都有数值;
 * 但左右两个数字有可能也是一个解析结果,无论何种类型,都是Expression类的实现类
 */
open class SymbolExpression(var left: Expression, var right: Expression) : Expression() {
    //这里是默认实现,具体的让其子类: 加法/减法表达式去做
    override fun interpret(exp: HashMap<String, Int>): Int {
        return 0
    }
}
//加法解析器 - 非终结表达式
class AddExpression(left: Expression, right: Expression) : SymbolExpression(left, right) {
    /**
     * 处理相加
     * exp 仍然是 {a=10, b=20, c=5}
     * super.left.interpreter(exp): 返回 left 表达式对应的值 a=10
     * super.right.interpreter(exp): 返回 right 表达式对应的值 b=20
     * 然后让左右两边数值相加
     */
    override fun interpret(exp: HashMap<String, Int>): Int {
        return super.left.interpret(exp) + super.right.interpret(exp)
    }
}
//减法解析器 - 非终结表达式
class SubExpression(left: Expression, right: Expression) : SymbolExpression(left,right) {
    //减法解释: 让两边的数值相减
    override fun interpret(exp: HashMap<String, Int>): Int {
        return super.left.interpret(exp) - super.right.interpret(exp)
    }
}
//变量名称的解析器 - 终结表达式
class VarExpression(var key: String) : Expression() {
    /**
     * interpreter() 根据变量的名称 返回对应的值
     * key 公式的参数,构造传入 如: a,b
     * @param exp 公式的键和值 : {a: 10 b: 20}
     * @return 获取存入变量名称对应的数值
     */
    override fun interpret(exp: HashMap<String, Int>): Int {
        return exp[key]!!
    }
}
//计算器 - Context 上下文
class Calculator(expStr: String) {
    //定义表达式
    var expression: Expression


    init {
        //安排运算先后顺序
        val stack: Stack<Expression> = Stack() //expStr = a + b
        //表达式拆分成字符数组
        val charArray = expStr.toCharArray() //[a, +, b]
        //左右符号
        var left: Expression
        var right: Expression
        var i = 0
        //遍历字符数组,即遍历[a, +, b]
        while (i < charArray.size) {
            //针对不同的情况,做处理
            when (charArray[i]) {
                '+' -> {
                    //左边的数字先入栈
                    left = stack.pop()// 从 stack 取出 left => 'a'
                    //右边的入栈
                    right = VarExpression(charArray[++i].toString()) //从 stack 取出 right => 'a'
                    //出栈加法运算
                    stack.push(AddExpression(left, right)) //根据得到的left 和 right 构建AddExpression
                }
                '-' -> {
                    //左边的数字先入栈
                    left = stack.pop()
                    //右边的入栈
                    right = VarExpression(charArray[++i].toString())
                    //出栈减法运算
                    stack.push(SubExpression(left, right))
                }
                //部署加减符号的,就是变量了,直接出栈
                else -> stack.push(VarExpression(charArray[i].toString()))
            }
            i++
        }
        //当遍历完整个 CharArray 数组后, 栈就得到了最后的表达式;
        this.expression = stack.pop()
    }


    //最后将公式键值绑定, 传递给 expression 的interpret 进行解释执行
    fun run(exp: HashMap<String, Int>): Int {
        return this.expression.interpret(exp)
    }
}

程序运行结果

请输入运算的表达式: a+b+c-d+e
请输入: a 的值: 10
请输入: b 的值: 11
请输入: c 的值: 1
请输入: d 的值: 2
请输入: e 的值: 3
运算结果: a+b+c-d+e = 23

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值