首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【设计模式】行为型模式-第 3 章第 3 讲【解释器模式】

【设计模式】行为型模式-第 3 章第 3 讲【解释器模式】

作者头像
跟着飞哥学编程
发布2022-12-02 15:53:14
2900
发布2022-12-02 15:53:14
举报

目录

什么叫解释器模式?

一、目的

二、实现

1、先来看一下解释器模式的通用类图 2-1

 2、代码实现案例

 2.1、创建一个抽象表达式接口,定义一个解释器方法。

 2.2、创建一个终结符表达式,实现抽象表达式,重写解释器方法。

2.5、创建一个客户端,测试类 TestInterpreDesign.java

 三、解释器模式的适用情况和应用案例

四、 解释器模式的优缺点


什么叫解释器模式?

 解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

计算机用来解释句子或表达式。当我们需要编写一系列处理这种需求的代码时,首先要知道句子或表达式的结构,要有一个表达式或句子的内部表示。可以理解为将相似性质的对象集合在一起。

一、目的

解释器模式定义语法的表示以及该语法的对应解释。

二、实现

解释器模式使用组合模式来定义对象结构的内部表示。

1、先来看一下解释器模式的通用类图 2-1

 图 2-1

解释器模式由4个部分组成:

  • Context(上下文环境):Context 用于封装解释器的全局信息,所有具体的解释器都要访问 Context。
  • AbstractExpression(抽象表达式):一个抽象类或者接口,声明执行的解释方法,由所有具体的解释器实现。
  • TerminalExpression(终结符表达式):实现与语法的终结符相关的操作。终结符表达式必须始终被实现和实例化,因为它表示表达式的结尾。
  • NonTerminalExpression(非终结符表达式):这是实现语法的不同规则或符号的类。对于每一个语法都应该创建一个类。

这里可能有人还是不太明白啥叫终结符表达式,啥叫非终结符表达式。下面我举例说明一下:

TerminalExpression(终结符表达式)

实现文法中与终结符有关的解释操作。文法中每一个终结符都有一个具体的终结符表达式与之对应。比如我们的 A=B+C 运算,B和C就是终结符,对应的解析 B 和 C 的解释器就是终结符表达式。

NonTerminalExpression(非终结符表达式)

实现文法中与非终结符有关的解释操作。文法中的每一条规则都对应一个非终结符表达式。非终结符表达式一般是文法中的运算符或者关键字,如 A=B+C 中 + 号就是非终结符表达式,解析 + 号的解释器就是一个非终结符表达式。 


 2、代码实现案例

 我们利用解释器模式来解析带有一个变量的简单函数 

f(x)
f(x)

波兰表示法也叫前缀表示法,我们普通的表示法叫中缀表示法,所以逆波兰就是后缀表示法。

这里我们为了简单,选择 逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法

这里顺道普及一下 逆波兰表示法的小知识哈……这种表示方法的好处就是不需要使用括号。

  • 例如表达“三加四”时,写作“3 4 +”,而不是“3 + 4”。
  • 如果有多个操作符,操作符置于第二个操作数的后面,所以常规中缀记法的“3 - 4 + 5”在逆波兰记法中写作“3 4 - 5 +”:先3减去4,再加上5。

逆波兰表达式的解释器一般是基于堆栈的

解释过程一般是:操作数入栈;遇到操作符时,操作数出栈,求值,将结果入栈;当一遍后,栈顶就是表达式的值。因此逆波兰表达式的求值使用堆栈结构很容易实现,和能很快求值。

 2.1、创建一个抽象表达式接口,定义一个解释器方法。

package com.zhaoyanfei.designpattern.InterpreterPattern;

/**
 * 抽象表达式接口 Expression
 * @Date 2022年10月15日
 * @author zhaoYanFei
 *
 */
public interface Expression {

	public float interpret();
	
}

 2.2、创建一个终结符表达式,实现抽象表达式,重写解释器方法。

Number 数字类解释所有数字。

package com.zhaoyanfei.designpattern.InterpreterPattern;


/**
 * @description 终结符表达式 Number
 * @Date 2022年10月15日
 * @author zhaoYanFei
 *
 */
public class Number implements Expression {

	private float number;
	
	public Number(float number) {
		this.number = number;
	}

	@Override
	public float interpret() {
		return number;
	}

}

 2.3、创建一个非终结符表达式,实现抽象表达式,重写解释器方法。

Operate 操作符类就是我们的组合表达式。

这里我们写一个加法和一个减法运算的表达式。

package com.zhaoyanfei.designpattern.InterpreterPattern;


/**
 * 
 * @description 加法(操作符)运算的非终结符表达式 Plus
 * @Date 2022年10月15日
 * @author zhaoYanFei
 *
 */
public class Plus implements Expression {

	Expression left;
	Expression right;
	
	public Plus(Expression left,Expression right) {
		this.left = left;
		this.right = right;
	}
	
	@Override
	public float interpret() {
		return left.interpret() + right.interpret();
	}

}

类似地实现一个减法操作符的非终结表达式

package com.zhaoyanfei.designpattern.InterpreterPattern;


/**
 * 
 * @description 减法(操作符)运算的非终结符表达式 Minus
 * @Date 2022年10月15日
 * @author zhaoYanFei
 *
 */
public class Minus implements Expression {

	Expression left;
	Expression right;
	
	public Minus(Expression left,Expression right) {
		//比如:逆波兰运算法,这里需要注意。3 2 -,表示3-2的运算,这里由于栈是先进后出,所以pop先取出2(在栈的左边),3(栈的的右边)
		this.left = left;
		this.right = right;
	}
	
	@Override
	public float interpret() {
		//减法运算时,这里需要注意顺序
		return right.interpret() - left.interpret();
	}

}

 2.4、创建一个环境类,定义解析文法。 

 现在我们定义上下文环境的类,也就是构建我们的语法树。

这里顺道解释一下栈的基本概念。以下是来源百度百科的定义


栈(stack)在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。 栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。 栈是只能在某一端插入和删除的特殊线性表。用桶堆积物品,先堆进来的压在底下,随后一件一件往上堆。取走时,只能从上面一件一件取。读和取都在顶部进行,底部一般是不动的。 栈就是一种类似桶堆积物品的数据结构,进行删除和插入的一端称栈顶,另一端称栈底 插入一般称为进栈,删除则称为退栈。 栈也称为后进先出表


Java 中栈 java.util.Stack 的用法:

方法

修饰符和类型

方法描述

empty()

boolean

测试堆栈是否为空

push(E item)

E

把元素压入堆栈顶部

pop()

E

移除堆栈顶部的对象,并作为此函数的值返回该对象

peek()

E

查看堆栈顶部的对象,但不从堆栈中移除它

search(Object o)

int

返回对象在堆栈中的位置,以1为基数

下面就是我们具体上下文环境类的代码:

package com.zhaoyanfei.designpattern.InterpreterPattern;

import java.util.Stack;

/**
 * 
 * @description 上下文环境 ExpressionContext
 * @Date 2022年10月15日
 * @author zhaoYanFei
 *
 */
public class ExpressionContext {

	public float calculate(String expression) {
		//初始一个空栈
		Stack<Expression> stack = new Stack<Expression>();
		float result = 0;
		for(String token : expression.split(" ")) {
			if(isOperate(token)) {//判断是操作符,则从栈中取出之前入栈的值作为左边表达式
				Expression exp = null;
				if(token.equals("+")) {
					exp = stack.push(new Plus(stack.pop(), stack.pop()));
				}else if(token.equals("-")) {
					exp = stack.push(new Minus(stack.pop(), stack.pop()));
				}
				if(exp!=null) {
					result = exp.interpret();
					stack.push(new Number(result));
				}
			}
			if(isNumber(token)) {//判断是数值,则直接进栈
				stack.push(new Number(Float.parseFloat(token)));
			}
		}
		return result;
	}

	/**
	 * 判断元素是否为数值
	 * @param token
	 * @return
	 */
	private boolean isNumber(String token) {
		try {
			Float.parseFloat(token);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}
	
	/**
	 * 判断元素是否为操作符
	 * @param token
	 * @return
	 */
	private boolean isOperate(String token) {
		if("+".equals(token) || "-".equals(token)) {
			return true;
		}else {
			return false;
		}
	}
}

2.5、创建一个客户端,测试类 TestInterpreDesign.java

package com.zhaoyanfei.designpattern.InterpreterPattern;


/**
 * 
 * @description 客户端测试类 TestInterpreDesign
 * @Date 2022年10月15日
 * @author zhaoYanFei
 *
 */
public class TestInterpreDesign {

	public static void main(String[] args) {
		ExpressionContext expressionContext = new ExpressionContext();
		System.out.println(expressionContext.calculate("2 3 +"));
		System.out.println(expressionContext.calculate("4 3 -"));
		System.out.println(expressionContext.calculate("4 3 - 2 +"));
	}
	
}

 三、解释器模式的适用情况和应用案例

解释器模式适用于表达式被解释并转换为其内部表示的情况。内部表示是基于组合模式的,因此解释器模式不适用于复杂的语法。

  • 需要重复解析的问题可以考虑解释器模式。
  • 需要解释一些简单语法的可以考虑解释器模式。

Java 在 java.util.Parser 中实现了解释器模式,它用于解释正则表达式。

//定义一个正则表达式,任意个a字母和一个b字母
Pattern p = Pattern.compile("a*b");
Matcher matcher = p.matcher("aab");
boolean matches = matcher.matches();
System.out.println(matches);

注:解释器模式在实际项目中很少用到,Java本身定义了很多解释器,比如上面提到的Pattern。

四、 解释器模式的优缺点

优点

  • 能够很容易地改变和扩展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。
  • 比较容易实现文法,因为定义抽象语法树中各个节点地类的实现大体类似,这些类都易于直接编写。

缺点

  • 解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。
  • 易引起类膨胀。
  • 可利用的场景较少。
  • 解释器模式采用递归调用方法。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、目的
  • 二、实现
    • 1、先来看一下解释器模式的通用类图 2-1
      •  2、代码实现案例
        •  2.1、创建一个抽象表达式接口,定义一个解释器方法。
        •  2.2、创建一个终结符表达式,实现抽象表达式,重写解释器方法。
        • 2.5、创建一个客户端,测试类 TestInterpreDesign.java
    •  三、解释器模式的适用情况和应用案例
    • 四、 解释器模式的优缺点
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档