乐趣区

关于设计模式:设计模式学习笔记十八解释器模式

1 概述

1.1 引言

解释器模式是一种应用频率较低然而学习难度较大的设计模式,用于形容如何应用面向对象语言形成一个简略的语言解释器。某些状况下可能须要自定义一个新语言,这种语言具备本人的文法规定,这时能够应用解释器模式进行设计,比方模仿机器人的控制程序,每一条指令对应一个动作,通过解释输出的指令来实现对机器人的管制。上面先来看一些术语定义。

1.2 相干术语

1.2.1 文法规定

文法规定是用于描述语言的语法的规定,比方,汉语中一个句子的文法规定为:

主 谓 宾

这就是句子的文法规定,同样计算机语言也有本人的文法规定。

1.2.2 BNF 符号

BNF 是 Backus-Naur Form 的缩写,是由 John Backus 以及 Peter Naur 首次引入的一种形式化符号来形容给定语言的语法,BNF 中定义的局部符号如下:

  • ::=:示意 定义为,右边的语言单位能够通过左边进行阐明和定义
  • |:示意 或者
  • "':双引号或单引号外面的字符串示意字符串自身

1.2.3 终结符与非终结符

在以下的模仿形容机器人挪动的文法规定中:

expression ::= direction action distance | composite   // 表达式
composite ::= expression 'and' expression              // 复合表达式
direction ::= 'up' | 'down' | 'left'| 'right'          // 挪动方向
action ::= 'move' | 'run'                              // 挪动形式
distance ::= an integer                                // 挪动间隔

定义了 5 条文法规则,对应 5 个语言单位,这些语言单位能够分为:

  • 终结符(也叫终结符表达式):语言的最小组成单位,不能再拆分,比方下面的 directionaction
  • 非终结符(也叫非终结符表达式):一个残缺的句子,蕴含一些列终结符或非终结符

1.2.4 形象语法树

除了应用文法规定定义一个语言外,还能应用一种叫形象语法树的直观形式示意,例如表达式:

1 / 2 * 3 - 4 + 1

能够通过如下形象语法树定义:

在该形象语法树中,能够通过终结符 value 以及非终结符 operation 组成简单的语句,终结符作为形象语法树的叶子,非终结符作为非叶子节点,能够将终结符或者蕴含终结符与非终结符的节点作为子节点。

1.3 定义

解释器模式:定义一个语言的文法,并且建设一个解释器来解释该语言中的句子。

这里的语言指的是应用规定格局以及语法的代码。解释器模式是一品种行为型模式。

1.4 结构图

1.5 角色

  • AbstractExpression(形象表达式):申明了形象的解释操作,是所有终结符表达式以及非终结符表达式的父类
  • TerminalExpression(终结符表达式):形象表达式的子类,实现了与文法规定中的终结符相关联的解释操作,句子中的每一个终结符都是该类的一个实例,通常只有少数几个终结符表达式类
  • NonterminalExpression(非终结符表达式):也是形象表达式的子类,实现了文法规定中非终结符的解释操作,因为非终结符表达式能够蕴含非终结符表达式以及终结符表达式,因而个别通过递归形式实现解释
  • Context(环境类):用于存储解释器之外的一些全局信息,通常它长期存储须要解释的语句

2 典型实现

2.1 步骤

  • (可选)定义环境类:首先对环境类进行定义,应用汇合存储相干的全局或公共信息,用于在具体解释时获取,如果毋庸全局信息则环境类能够省略
  • 定义形象表达式类:接口 / 抽象类,申明形象解释操作
  • 定义终结符表达式类:继承 / 实现形象表达式,定义终结符的解释操作
  • 定义非终结符表达式类:继承 / 实现形象表达式,定义非终结符解释操作,个别通过递归解决

2.2 环境类

这里临时不须要环境类,为了兼容定义一个空类:

class Context{}

2.3 形象表达式

蕴含形象解释操作方法:

interface AbstractExpression
{void interpret(Context context);
}

2.4 终结符表达式

解释终结符表达式:

class TerminalExpression implements AbstractExpression
{
    @Override
    public void interpret(Context context)
    {System.out.println("终结符解析");
    }
}

2.5 非终结符表达式

class NonterminalExpression implements AbstractExpression
{
    private AbstractExpression left;
    private AbstractExpression right;

    public NonterminalExpression(AbstractExpression left,AbstractExpression right)
    {
        this.left = left;
        this.right = right;
    }

    @Override
    public void interpret(Context context)
    {System.out.println("非终结符解析");
        if(left != null)
            left.interpret(context);
        if(right != null)
            right.interpret(context);
    }
}

解释非终结符时个别须要递归解决,这里模仿了非终结符左右两边的表达式操作。

2.6 客户端

public static void main(String[] args) 
{AbstractExpression expression1 = new TerminalExpression();
    AbstractExpression expression2 = new TerminalExpression();
    AbstractExpression expression3 = new NonterminalExpression(expression1,expression2);
    expression3.interpret(null);
}

定义两个终结符表达式与一个非终结符表达式,最初对非终结符表达式进行解释。

3 实例

对机器人挪动指令进行解释,挪动的语法表白如下:方向 形式 间隔,方向包含上下左右四个方向,形式包含跑以及个别挪动,间隔为一个整数,一条挪动指令能够组合多条子挪动指令,应用解释器模式进行设计。

设计如下:

  • 环境类:这里为空
  • 形象表达式类:AbstractNode
  • 终结符表达式类:DirectionNode+ActionNode+DistanceNode
  • 非终结符表达式类:AndNode+SentenceNode

形象表达式类如下:

interface AbstractNode
{String interpret(String str);
}

终结符表达式类:

class DirectionNode implements AbstractNode
{
    private static final Map<String,String> strs;
    static
    {strs = new HashMap<>();
        strs.put("up", "向上");
        strs.put("down", "向下");
        strs.put("left", "向左");
        strs.put("right", "向右");
    }
    @Override
    public String interpret(String str)
    {return strs.containsKey(str) ? strs.get(str) : "有效操作";
    }
}

class ActionNode implements AbstractNode
{
    private static final Map<String,String> strs;
    static
    {strs = new HashMap<>();
        strs.put("move", "挪动");
        strs.put("run", "疾速挪动");
    }
    @Override
    public String interpret(String str)
    {return strs.containsKey(str) ? strs.get(str) : "有效操作";
    }
}

class DistanceNode implements AbstractNode
{
    @Override
    public String interpret(String str)
    {return str;}
}

依据对应的字符串返回相应的字符串即可。

非终结符表达式类:

class SentenceNode implements AbstractNode
{private final AbstractNode direction = new DirectionNode();
    private final AbstractNode action = new ActionNode();
    private final AbstractNode distance = new DistanceNode();
    @Override
    public String interpret(String s)
    {String [] str = s.split(" ");
        return direction.interpret(str[0])+action.interpret(str[1])+distance.interpret(str[2]);
    }
}

class AndNode implements AbstractNode
{
    @Override
    public String interpret(String s)
    {if(s.contains("and"))
        {int index = s.indexOf("and");
            String leftStr = s.substring(0, index-1);
            String rightStr = s.substring(index+4);
            AbstractNode left = (leftStr.contains("and") ? new AndNode() : new SentenceNode());
            AbstractNode right = (rightStr.contains("and") ? new AndNode() : new SentenceNode());
            return left.interpret(leftStr) + "再" + right.interpret(rightStr);
        }
        return new SentenceNode().interpret(s);
    }
}

其中 AndNode 采取了递归进行解释操作,如果宰割后的字符串还含有 and 则赋值为AndNode,否则为SentenceNode

测试:

public static void main(String[] args) 
{AbstractNode node = new AndNode();
    System.out.println(node.interpret("up move 5 and down run 10 and down move 10 and left run -9"));
}

输入如下:

4 扩大

如果我的项目中须要对数据表达式进行剖析与计算,能够间接应用现有的库,比方:

  • Expression4J
  • MESP
  • Jep
  • Fel

等等,上面以 Jep 为例演示该库的应用办法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库,用户能够以字符串模式输出一个任意公式,而后疾速计算出后果。Jep 反对用户自定义变量,常量,函数,包含很多罕用的数学函数以及常量。

首先下载 JAR 包依赖,例子如下:

import com.singularsys.jep.*;

public class Test
{public static void main(String[] args) throws Exception
    {Jep jep=new Jep();
        // 定义要计算的数据表达式
        String interestOnDeposit="本金 * 利率 * 工夫";
        // 给相干变量赋值
        jep.addVariable("本金",10000);
        jep.addVariable("利率",0.038);
        jep.addVariable("工夫",2);
        jep.parse(interestOnDeposit);     // 解析表达式
        Object accrual=jep.evaluate();    // 计算
        System.out.println("贷款利息:"+accrual);
    }
}

5 次要长处

  • 扩展性好:因为解释器中应用类来示意语言的文法规定,因而能够通过继承等机制来扭转或扩大文法
  • 便于实现语言:每一条文法规定都能够示意为一个类,因而能够不便地实现一个简略的语言
  • 实现文法容易:形象语法树中每一个表达式节点类的实现形式都是相似的,这些类的代码编写都不会特地简单,还能够通过一些工具主动生成节点类代码
  • 减少解释表达式不便:如果用户须要减少新的解释表达式只须要对应减少一个新的终结符表达式或非终结符表达式,原有表达式类毋庸批改

6 次要毛病

  • 简单文法难以保护:在解释器模式中,每一条规定至多须要定义一个类,因而如果一个语言蕴含太多文法规定会导致类个数急增,导致系统难以治理和保护,能够思考应用语法分析程序来取代解释器模式
  • 执行效率低:因为解释器模式中应用了大量的循环和递归调用,因而在解释较为简单的句子时速度很慢,而且代码的调试过程也比拟麻烦

7 实用场景

  • 能够将一个须要解释执行的语言中的句子示意为一个形象语法树
  • 一些反复呈现的问题能够用一种简略的语言来形容
  • 一个语言的文法较为简单
  • 执行效率不是关键问题

8 总结

如果感觉文章难看,欢送点赞。

同时欢送关注微信公众号:氷泠之路。

退出移动版