行为型:设计模式之解释器模式(二十二)

Posted 官小飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了行为型:设计模式之解释器模式(二十二)相关的知识,希望对你有一定的参考价值。

 虽然目前计算机编程语言有好几百种,但有时候我们还是希望能用一些简单的语言来实现一些特定的操作,我们只要向计算机输入一个句子或文件,它就能够按照预先定义的文法规则来对句子或文件进行解释,从而实现相应的功能。例如提供一个简单的加法/减法解释器,只要输入一个加法/减法表达式,它就能够计算出表达式结果,如图1所示,当输入字符串表达式为“1 + 2 + 3 – 4 + 1”时,将输出计算结果为3

1  加法/减法解释器示意图

       我们知道,像C++JavaC#等语言无法直接解释类似“1+ 2 + 3 – 4 + 1”这样的字符串(如果直接作为数值表达式时可以解释),我们必须自己定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言。在实际开发中,这些简单的自定义语言可以基于现有的编程语言来设计,如果所基于的编程语言是面向对象语言,此时可以使用解释器模式来实现自定义语言。


1 机器人控制程序

       Sunny软件公司欲为某玩具公司开发一套机器人控制程序,在该机器人控制程序中包含一些简单的英文控制指令,每一个指令对应一个表达式(expression),该表达式可以是简单表达式也可以是复合表达式,每一个简单表达式由移动方向(direction),移动方式(action)和移动距离(distance)三部分组成,其中移动方向包括上(up)、下(down)、左(left)、右(right);移动方式包括移动(move)和快速移动(run);移动距离为一个正整数。两个表达式之间可以通过与(and)连接,形成复合(composite)表达式。

       用户通过对图形化的设置界面进行操作可以创建一个机器人控制指令,机器人在收到指令后将按照指令的设置进行移动,例如输入控制指令:up move 5,则“向上移动5个单位”;输入控制指令:down  run 10 and left move 20,则“向下快速移动10个单位再向左移动20个单位”。

       Sunny软件公司开发人员决定自定义一个简单的语言来解释机器人控制指令,根据上述需求描述,用形式化语言来表示该简单语言的文法规则如下:

expression ::= direction action distance | composite //表达式

composite ::= expression 'and' expression //复合表达式

direction ::= 'up' | 'down' | 'left' | 'right' //移动方向

action ::= 'move' | 'run' //移动方式

distance ::= an integer //移动距离

       上述语言一共定义了五条文法规则,对应五个语言单位,这些语言单位可以分为两类,一类为终结符(也称为终结符表达式),例如directionactiondistance,它们是语言的最小组成单位,不能再进行拆分;另一类为非终结符(也称为非终结符表达式),例如expressioncomposite,它们都是一个完整的句子,包含一系列终结符或非终结符。

       我们根据上述规则定义出的语言可以构成很多语句,计算机程序将根据这些语句进行某种操作。为了实现对语句的解释,可以使用解释器模式,在解释器模式中每一个文法规则都将对应一个类,扩展、改变文法以及增加新的文法规则都很方便,下面就让我们正式进入解释器模式的学习,看看使用解释器模式如何来实现对机器人控制指令的处理。

2 文法规则和抽象语法树

       解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。在正式分析解释器模式结构之前,我们先来学习如何表示一个语言的文法规则以及如何构造一棵抽象语法树。

       在前面所提到的加法/减法解释器中,每一个输入表达式,例如“1 + 2 + 3 – 4 + 1”,都包含了三个语言单位,可以使用如下文法规则来定义:

expression ::= value | operation

operation ::= expression '+' expression | expression '-'  expression

value ::= an integer //一个整数值

       该文法规则包含三条语句,第一条表示表达式的组成方式,其中valueoperation是后面两个语言单位的定义,每一条语句所定义的字符串如operationvalue称为语言构造成分或语言单位,符号“::=”表示“定义为”的意思,其左边的语言单位通过右边来进行说明和定义,语言单位对应终结符表达式和非终结符表达式。如本规则中的operation是非终结符表达式,它的组成元素仍然可以是表达式,可以进一步分解,而value是终结符表达式,它的组成元素是最基本的语言单位,不能再进行分解。

       在文法规则定义中可以使用一些符号来表示不同的含义,如使用“|”表示或,使用“”和“”表示组合,使用“*”表示出现0次或多次等,其中使用频率最高的符号是表示“或”关系的“|”,如文法规则“boolValue ::= 0 | 1”表示终结符表达式boolValue的取值可以为0或者1

       除了使用文法规则来定义一个语言,在解释器模式中还可以通过一种称之为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例,如加法/减法表达式语言中的语句“1+ 2 + 3 – 4 + 1”,可以通过如图2所示抽象语法树来表示:

22-2  抽象语法树示意图

       在该抽象语法树中,可以通过终结符表达式value和非终结符表达式operation组成复杂的语句,每个文法规则的语言实例都可以表示为一个抽象语法树,即每一条具体的语句都可以用类似图22-2所示的抽象语法树来表示,在图中终结符表达式类的实例作为树的叶子节点,而非终结符表达式类的实例作为非叶子节点,它们可以将终结符表达式类的实例以及包含终结符和非终结符实例的子表达式作为其子节点。抽象语法树描述了如何构成一个复杂的句子,通过对抽象语法树的分析,可以识别出语言中的终结符类和非终结符类。


3 解释器模式概述

       解释器模式是一种使用频率相对较低但学习难度较大的设计模式,它用于描述如何使用面向对象语言构成一个简单的语言解释器。在某些情况下,为了更好地描述某一些特定类型的问题,我们可以创建一种新的语言,这种语言拥有自己的表达式和结构,即文法规则,这些问题的实例将对应为该语言中的句子。此时,可以使用解释器模式来设计这种新的语言。对解释器模式的学习能够加深我们对面向对象思想的理解,并且掌握编程语言中文法规则的解释过程。

       解释器模式定义如下:

解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。

       由于表达式可分为终结符表达式和非终结符表达式,因此解释器模式的结构与组合模式的结构有些类似,但在解释器模式中包含更多的组成元素,它的结构如图3所示:

3  解释器模式结构图

       在解释器模式结构图中包含如下几个角色:

       ● AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。

       ● TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。

       ● NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。

       ● Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

       在解释器模式中,每一种终结符和非终结符都有一个具体类与之对应,正因为使用类来表示每一条文法规则,所以系统将具有较好的灵活性和可扩展性。对于所有的终结符和非终结符,我们首先需要抽象出一个公共父类,即抽象表达式类,其典型代码如下所示:

[java]  view plain copy print ?
  1. abstract class AbstractExpression   
  2.        public  abstract void interpret(Context ctx);  
  3.   

       终结符表达式和非终结符表达式类都是抽象表达式类的子类,对于终结符表达式,其代码很简单,主要是对终结符元素的处理,其典型代码如下所示:

[java]  view plain copy print ?
  1. class TerminalExpression extends  AbstractExpression   
  2.        public  void interpret(Context ctx)   
  3.               //终结符表达式的解释操作  
  4.          
  5.   

       对于非终结符表达式,其代码相对比较复杂,因为可以通过非终结符将表达式组合成更加复杂的结构,对于包含两个操作元素的非终结符表达式类,其典型代码如下:

[java]  view plain copy print ?
  1. class NonterminalExpression extends  AbstractExpression   
  2.        private  AbstractExpression left;  
  3.        private  AbstractExpression right;  
  4.         
  5.        public  NonterminalExpression(AbstractExpression left,AbstractExpression right)   
  6.               this.left=left;  
  7.               this.right=right;  
  8.          
  9.         
  10.        public void interpret(Context ctx)   
  11.               //递归调用每一个组成部分的interpret()方法  
  12.               //在递归调用时指定组成部分的连接方式,即非终结符的功能  
  13.               
  14.   

       除了上述用于表示表达式的类以外,通常在解释器模式中还提供了一个环境类Context,用于存储一些全局信息,通常在Context中包含了一个HashMapArrayList等类型的集合对象(也可以直接由HashMap等集合类充当环境类),存储一系列公共信息,如变量名与值的映射关系(key/value)等,用于在进行具体的解释操作时从中获取相关信息。其典型代码片段如下:

[java]  view plain copy print ?
  1. class Context   
  2.      private HashMap map = new HashMap();  
  3.      public void assign(String key, String value)   
  4.          //往环境类中设值  
  5.        
  6. public String  lookup(String key)   
  7.          //获取存储在环境类中的值  
  8.        
  9.   

       当系统无须提供全局公共信息时可以省略环境类,可根据实际情况决定是否需要环境类。

思考

绘制加法/减法解释器的类图并编写核心实现代码。



4 完整解决方案

为了能够解释机器人控制指令,Sunny软件公司开发人员使用解释器模式来设计和实现机器人控制程序。针对五条文法规则,分别提供五个类来实现,其中终结符表达式directionactiondistance对应DirectionNode类、ActionNode类和DistanceNode类,非终结符表达式expressioncomposite对应SentenceNode类和AndNode类。

      我们可以通过抽象语法树来表示具体解释过程,例如机器人控制指令“down run 10 and left move 20”对应的抽象语法树如图4所示:

4   机器人控制程序抽象语法树实例

      机器人控制程序实例基本结构如图5所示:

22-5   机器人控制程序结构图

      在图22-5中,AbstractNode充当抽象表达式角色,DirectionNodeActionNodeDistanceNode充当终结符表达式角色,AndNodeSentenceNode充当非终结符表达式角色。完整代码如下所示:

[java]  view plain copy
  1. //注:本实例对机器人控制指令的输出结果进行模拟,将英文指令翻译为中文指令,实际情况是调用不同的控制程序进行机器人的控制,包括对移动方向、方式和距离的控制等  
  2. import java.util.*;  
  3.   
  4. //抽象表达式  
  5. abstract class AbstractNode   
  6.     public abstract String interpret();  
  7.   
  8.   
  9. //And解释:非终结符表达式  
  10. class AndNode extends AbstractNode   
  11.     private AbstractNode left; //And的左表达式 

    以上是关于行为型:设计模式之解释器模式(二十二)的主要内容,如果未能解决你的问题,请参考以下文章

    JAVA设计模式(22):行为型-解释器模式(Interpreter)

    Java经典23种设计模式之行为型模式

    行为型模式之解释器模式

    设计模式 行为型模式 -- 解释器模式

    行为型模式--解释器模式

    Python设计模式:行为型