解释器模式
定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的语言指的是规定格式和语法的代码,是一种对象行为型模式。
文法规则和抽象语法树
文法规则
(1)表达式的组成方式,value和operation是后面两个语言单位的定义,每一个语句所定义的字符串如value和operation称为语言构造成分或语言单位,符号::=表示定义为的意思,左边的语言单位通过右边来进行说明和定义。语言单位分为终结符表达式和非终结表达式,比如operation是非终结表达式,他的组成元素仍然可以是表达式,而value是终结符表达式,不能进行再分解。
(2)可以用一些符号表示不同的含义,"|“使用频率最高,为“或”的关系,”{","}","*"表示0次或者多次
抽象语法树
除了文法规则,还可以通过一种称之为抽象语法树的图形方式直观表达语言的构成。
结构图
(1)AbstractExpression(抽象表达式)在抽象表达式中声明了抽象的解释操作,是所有终结表达式和非终结表达式的父类。
(2)TerminaltExpression(终结符表达式)抽象表达式的子类,实现了与文法中的终结符相关的解释操作,在句子中每一个终结符都是该类的实例,一个解释器模式只有少数几个终结符表达式类,但是他们的实例可以通过非终结表达式组成较为复杂的句子。
(3)NonterminaltExpression(非终结表达式)同样也是抽象表达式的子类,实现了非终结符的解释操作,由于在非终结表达式可以包含终结表达式,也可以包含非终结表达式,因此其解释操作一般通过递归的方案完成。
(4)Context(环境类)称之为上下文,用于存储解释器之外的全局信息,通常临时存储了需要解释的语句。
解释器模式中,每一个终结符和非终结符都有具体的类与之对应。
package com.learn.designmode.mode.interpreter;
import java.util.HashMap;
/**
* 抽象表达式
*/
public abstract class AbstractExpression {
abstract void interpret(Context context);
}
/**
* 终结符表达式
*/
class TerminalExpression extends AbstractExpression{
@Override
void interpret(Context context) {
// 终结符表达式的解释操作
}
}
/**
* 非终结符表达式
*/
class NonterminalExpression extends AbstractExpression{
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left,AbstractExpression right){
this.left = left;
this.right = right;
}
@Override
void interpret(Context context) {
// 非终结符表达式的解释操作
}
}
/**
* 环境类(根据系统是否需要)
* 用于存储一些全局信息 key-value的形式
*/
class Context{
private HashMap hashMap = new HashMap();
public void addSign(String key,String value){
// 往环境变量设值
}
public String lookup(String key){
return (String) hashMap.get(key);
}
}
完整的解决方案
执行语句:down run 10 and left move 20
终结表达式
- driection(上下左右) ------- DriectionNode
- action(移动方式) ------- ActionNode
- distance(距离)------- DistanceNode
非终结表达式 - expression ------- SentenceNode
- composite ------- AndNode
代码
(本实例只是想语法转换为中文指令)
package com.learn.designmode.mode.interpreter.demo;
/**
* 抽象表达式
*/
public abstract class AbstractNode {
abstract String interpret();
}
/**
* composite(非终结符表达式)and解释
*/
class AndNode extends AbstractNode{
private AbstractNode left;
private AbstractNode right;
public AndNode(AbstractNode left,AbstractNode right){
this.left = left;
this.right = right;
}
@Override
String interpret() {
return left.interpret() + "再" + right.interpret();
}
}
/**
* composite(非终结符表达式) 简单句子解释
*/
class SentenceNode extends AbstractNode{
private AbstractNode direction;
private AbstractNode action;
private AbstractNode distance;
public SentenceNode(AbstractNode direction,AbstractNode action,AbstractNode distance){
this.direction = direction;
this.action = action;
this.distance = distance;
}
/** 简单句子的解释
* @return
*/
@Override
String interpret() {
return direction.interpret() + action.interpret() + distance.interpret();
}
}
/**
* 终结符表达式(方向解释)
*/
class DirectionNode extends AbstractNode{
private static final String UP = "up";
private static final String DOWN = "down";
private static final String LEFT = "left";
private static final String RIGHT = "right";
private String direction;
public DirectionNode(String direction){
this.direction = direction;
}
@Override
String interpret() {
if (UP.equalsIgnoreCase(direction)){
return "向上";
}else if (DOWN.equalsIgnoreCase(direction)){
return "向下";
}else if (LEFT.equalsIgnoreCase(direction)){
return "向左";
}else if (RIGHT.equalsIgnoreCase(direction)){
return "向右";
}else {
return "无效指令";
}
}
}
/**
* 终结符表达式(方式解释)
*/
class AcitonNode extends AbstractNode{
private static final String MOVE = "move";
private static final String RUN = "run";
private String action;
public AcitonNode(String action){
this.action = action;
}
@Override
String interpret() {
if (MOVE.equalsIgnoreCase(action)){
return "移动";
}else if (RUN.equalsIgnoreCase(action)){
return "快速移动";
}else {
return "无效指令";
}
}
}
/**
* 终结符表达式(距离解释)
*/
class DistanceNode extends AbstractNode{
private String distance;
public DistanceNode(String distance){
this.distance = distance;
}
@Override
String interpret() {
return distance;
}
}
package com.learn.designmode.mode.interpreter.demo;
import java.util.Stack;
public class InstructionHandler {
private AbstractNode abstractNode;
public void handler(String instruction){
AbstractNode left = null,right = null,direction = null,action = null,distance = null;
// 声明一个栈对象用于存储语法树
Stack stack = new Stack();
String[] words = instruction.split(" ");
for (int i = 0;i<words.length;i++){
// 本实例用栈处理命令,如果遇到and
// 则将其后面的三个单词拼接成一个Sentence作为右表达式,从栈中去除左表达式,用AndNode拼接
if (words[i].equalsIgnoreCase("and")){
left = (AbstractNode) stack.pop();
// 拼接后边的定义
String word1 = words[++ i];
direction = new DirectionNode(word1);
action = new AcitonNode(words[++i]);
distance = new DistanceNode(words[++i]);
right = new SentenceNode(direction,action,distance);
stack.push(new AndNode(left,right));
}else {
String word1 = words[i];
direction = new DirectionNode(word1);
action = new AcitonNode(words[++i]);
distance = new DistanceNode(words[++i]);
left = new SentenceNode(direction,action,distance);
stack.push(left);
}
}
this.abstractNode = (AbstractNode) stack.pop();
}
public String output(){
String result = abstractNode.interpret();
return result;
}
}
package com.learn.designmode.mode.interpreter.demo;
public class Client {
public static void main(String[] args) {
String instruction = "up move 5 and down run 10 and left move 5";
InstructionHandler instructionHandler = new InstructionHandler();
instructionHandler.handler(instruction);
String outString;
outString = instructionHandler.output();
System.out.println(outString);
}
}
再谈Context的作用
前面讲到Context是用来存储全局信息,通常作为参数传到所有表达式的interpret中,可以在Context对象中存储和访问表达式解释器的状态,向表达式解释器提供一些全局的,公共的数据,此外还可以在Context中增加一些所有表达式解释器都共有的功能,减轻解释器的职责。
下面再用一个例子说明Context的作用
文法规则
package com.learn.designmode.mode.interpreter.demo2;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public abstract class Node {
abstract void interpret(Context context);
abstract void excute();
}
/**
* 表达式节点类(非终结符表达式)(复合语句)
*/
class ExpressionNode extends Node{
private List<Node> list = new ArrayList<Node>();
@Override
void interpret(Context context) {
while (true){
// 判断有没有下一个指令
if (context.getCurrencyToken() == null){
break;
// 这里避免循环语句的END
}else if (context.getCurrencyToken().equals("END")){
context.skipToken("END");
break;
// 如果为其他标记,则解释标记并加入命令集合
}else {
Node commandNode = new CommandNode();
commandNode.interpret(context);
list.add(commandNode);
}
}
}
// 循环执行命令集合中的每一个命令
@Override
void excute() {
Iterator iterator = list.iterator();
while (iterator.hasNext()){
((Node)iterator.next()).excute();
}
}
}
// 语句命令节点(非终结符表达式)(简单语句)
class CommandNode extends Node{
private Node node;
@Override
void interpret(Context context) {
if (context.getCurrencyToken().equals("LOOP")){
node = new LoopCommandNode();
node.interpret(context);
}else {
node = new PrimitiveCommandNode();
node.interpret(context);
}
}
@Override
void excute() {
node.excute();
}
}
/**
* 基本命令节点,终结符表达式
*/
class PrimitiveCommandNode extends Node{
private String name;
private String text;
// 解释基本命令
@Override
void interpret(Context context) {
name = context.getCurrencyToken();
// 跳过当前字符
context.skipToken(name);
if (!name.equals("PRINT") && !name.equals("BREAK") && !name.equals("SPACE")){
System.out.println("非法命令");
}
// 如果是PRINT 则将下一个字符给text
if (name.equals("PRINT")){
text = context.getCurrencyToken();
context.nextToken();
}
}
// 执行
@Override
void excute() {
if (name.equals("PRINT")){
System.out.print(text);
}else if (name.equals("SPACE")){
System.out.print(" ");
}else if (name.equals("BREAK")){
System.out.println();
}
}
}
/**
* 循环命令节点,非终结符表达式(循环的复合语句以END结尾)
*/
class LoopCommandNode extends Node{
private int number;
private Node expressionNode;
@Override
void interpret(Context context) {
context.skipToken("LOOP");
number = context.currentNumber();
context.nextToken();
// 循环语句中的表达式
expressionNode = new ExpressionNode();
expressionNode.interpret(context);
}
@Override
void excute() {
for (int i = 0 ;i < number;i++){
expressionNode.excute();
}
}
}
总结
优点
(1)易于改变和扩展文法。解释器模式使用类来表示语言的文法规则,因此可以通过继承的关系扩展文法。
(2)每一个文法规则都可以表示为一个类,因此可以简单的实现一个简单的语言。
(3)实现文法较为容易。在抽象语法树中每一个表达式节点类的实现都是相似的,类的编写代码较为容易,还可以通过一些工具自动生成节点类代码。
(4)增加新的解释表达式较为方便,如果用户需要增加新的解释表达式只需要加一个继承抽象表达式的类。
缺点
(1)对于复杂的文法难以维护,在解释器中,每一条规则至少需要一个类,因此如果一个语言包含太多文法规则,类的个数将会极具的增加,导致系统难以维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
(2)执行效率低下,由于解释器使用了大量的循环和递归调用,因此实现符合文法其速度较慢。而已代码调试比较麻烦。
适用场景
(1)可以将一个需要执行的语言中的句子表示为一个抽象树
(2)一些重复出现的问题可以用一种简单的语言来进行表示
(3)一个语言的文法较为简单
(4)执行效率不是关键问题。高效的执行器不是直接通过解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。