21.2 解 决 方 案

21.2.1 使用解释器模式来解决问题

用来解决上述问题的一个合理的解决方案,就是使用解释器模式。那么什么是解释器模式呢?

1.解释器模式的定义

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

这里的文法,简单点说就是我们俗称的“语法规则”。

2.应用解释器模式来解决问题的思路

要想解决当xml的结构发生改变后,不用修改解析部分的代码,一个自然的思路就是要把解析部分的代码写成公共的,而且还要是通用的,能够满足各种xml取值的需要,比如,获取单个元素的值、获取多个相同名称的元素的值、获取单个元素的属性的值、获取多个相同名称的元素的属性的值,等等。

提示

要写成通用的代码,又有几个问题要解决,如何组织这些通用的代码?如何调用这些通用的代码?以何种方式来告诉这些通用代码,客户端的需要?

要解决这些问题,其中的一个解决方案就是解释器模式。在描述这个模式的解决思路之前,先解释两个概念,一个是解析器(不是指xml的解析器),另一个是解释器。

■ 这里的解析器,指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序,不是指xml的解析器。

■ 这里的解释器,指的是解释抽象语法树,并执行每个节点对应的功能的程序。

要解决通用解析xml的问题,第一步:需要先设计一个简单的表达式语言,在客户端调用解析程序的时候,传入用这个表达式语言描述的一个表达式,然后把这个表达式通过解析器的解析,形成一个抽象的语法树。

第二步:解析完成后,自动调用解释器来解释抽象语法树,并执行每个节点所对应的功能,从而完成通用的xml解析。

这样一来,每次当xml结构发生了更改,也就是在客户端调用的时候,传入不同的表达式即可,整个解析xml过程的代码都不需要再修改了。

21.2.2 解释器模式的结构和说明

解释器模式的结构如图21.1所示。

图片

图21.1 解释器模式结构图

■ AbstractExpression:定义解释器的接口,约定解释器的解释操作。

■ TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。

■ NonterminalExpression:非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的组合对象。可以有多种非终结符解释器。

■ Context:上下文,通常包含各个解释器需要的数据或是公共的功能。

■ Client:客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成为使用解释器对象描述的抽象语法树,然后调用解释操作。

21.2.3 解释器模式示例代码

(1)先看看抽象表达式的定义。非常简单,定义一个执行解释的方法。示例代码如下:

44c02693df6d4276831527041cb8a056

8b904b996c0e4286b1b7ab26c8b4cfe8

(2)再来看看终结符表达式的定义。示例代码如下:

7f6ce5f3c58b499abae92d1d7752e7c5

(3)接下来该看看非终结符表达式的定义了。示例代码如下:

0773a57c43484b559385087b26363f45

(4)上下文的定义。示例代码如下:

de300f2ee5f649bfa81aa57e3a0b71f7

(5)最后来看看客户端的定义。示例代码如下:

4e48cb8a00ec44c7aa7131bd6bf92147

看到这里,可能有些朋友会觉得,上面的示例代码里面什么都没有啊。这主要是因为解释器模式是和具体的语法规则联系在一起的,没有相应的语法规则,自然写不出对应的处理代码来。

但是这些示例还是有意义的,可以通过它们看出解释器模式实现的基本架子,只是没有具体的内部处理罢了。

21.2.4 使用解释器模式重写示例

通过上面的讲述可以看出,要使用解释器模式,一个重要的前提就是要定义一套语法规则,也称为文法。不管这套文法的规则是简单还是复杂,必须有这些规则,因为解释器模式就是按照这些规则来进行解析并执行相应的功能的。

1.为表达式设计简单的文法

为了通用,用root表示根元素,a、b、c、d等来代表元素,一个简单的xml如下:

5d567fa0e22f4d39b0097eaa3de3a3d8

约定表达式的文法如下。

■ 获取单个元素的值:从根元素开始,一直到想要获取值的元素,元素中间用“/”分隔,根元素前不加“/”。比如,表达式“root/a/b/c”就表示获取根元素下、a元素下、b元素下的c元素的值。

■ 获取单个元素的属性的值:要获取值的属性一定是表达式的最后一个元素的属性,在最后一个元素后面添加“.”然后再加上属性的名称。比如,表达式“root/a/b/c.name”就表示获取根元素下、a元素下、b元素下、c元素的name属性的值。

■ 获取相同元素名称的值,当然是多个,要获取值的元素一定是表达式的最后一个元素,在最后一个元素后面添加“$”。比如,表达式“root/a/b/d$”就表示获取根元素下、a元素下、b元素下的多个d元素的值的集合。

■ 获取相同元素名称的属性的值,当然也是多个:要获取属性值的元素一定是表达式的最后一个元素,在最后一个元素后面添加“$”,然后在后面添加“.”然后再加上属性的名称,在属性名称后面也添加“$”。比如,表达式“root/a/b/d$.id$”就表示获取根元素下、a元素下、b元素下的多个d元素的id属性的值的集合。

2.示例说明

为了示例的通用性,就使用上面这个简单的xml来实现功能,不去使用前面定义的具体的xml了,解决的方法是一样的。

另外一个问题,解释器模式主要解决的是“解释抽象语法树,并执行每个节点所对应的功能”,并不包含如何从一个表达式转换成为抽象的语法树。因此下面的范例就先来实现解释器模式所要求的功能。至于如何从一个表达式转换成为相应的抽象语法树,后面将会给出一个示例。

对于抽象的语法树这个树状结构,很明显可以使用组合模式来构建。解释器模式把需要解释的对象分成了两大类,一类是节点元素,就是可以包含其他元素的组合元素,比如非终结符元素,对应成为组合模式的Composite;另一类是终结符元素,相当于组合模式的叶子对象。解释整个抽象语法树的过程,也就是执行相应对象的功能的过程。

比如上面的xml,对应成为抽象语法树,可能的结构如图21.2所示:

图片

图21.2 xml对应的抽象语法树示意图

3.具体示例

从简单的开始,先来演示获取单个元素的值和单个元素的属性的值。在看具体代码前,先来看看此时系统的整体结构,如图21.3所示。

图片

图21.3 解释器模式示例的结构示意图

(1)定义抽象的解释器。

要实现解释器的功能,首先定义一个抽象的解释器,来约束所有被解释的语法对象,也就是节点元素和终结符元素都要实现的功能。示例代码如下:

5724009e1b2b49a2a96566bee055f8e9

(2)定义上下文。

上下文是用来封装解释器需要的一些全局数据,也可以在其中封装一些解释器的公共功能,相当于各个解释器的公共对象。示例代码如下:

a90eb56c69234385bfec09bfb7a703e2

feecd8a4e19f48fab658dc1ddf2b2b95

d9f5ab2d5c7d40ec8bd2975c65aa0d2c

在上下文中使用了一个工具对象XmlUtil来获取Document对象,就是Dom解析xml,获取相应的Document对象。示例代码如下:

4cda28f072584b9a8fe6791ebf8d3045

1a16f6ae6f9f4541bd8eea846ccf68f4

(3)定义元素作为非终结符对应的解释器。

接下来该看看如何解释执行中间元素了。首先这个元素相当于组合模式中的Composite对象,因此需要对子元素进行维护,另外这个元素的解释处理,只需要把自己找到,作为下一个元素的父元素就可以了。示例代码如下:

bb54c7b27f524e2c972eb13f53318549

94b9937e130f4e1bb3353437a96ba075

(4)定义元素作为终结符对应的解释器。

对于单个元素的处理,终结符有两种,一个是元素终结,另一个是属性终结。如果是元素终结,就是要获取元素的值;如果是属性终结,就是要获取属性的值。分别来看看是如何实现的。

先看看元素作为终结的解释器。示例代码如下:

c4ac8b6fd1cd46cfa448c82377be8e64

cbad48564572408f9f8b75384646cba2

(5)定义属性作为终结符对应的解释器。

接下来看看属性终结符的实现,就会比较简单,直接获取最后的元素对象,然后获取相应的属性的值。示例代码如下:

3b40f994c4484e0186aff2c22d1a1118

87d7497fa6e64b8eb91a738ef91bb467

(6)使用解释器。

定义好了各个解释器的实现,可以写个客户端来测试一下这些解释器对象的功能了。使用解释器的客户端的工作会比较多,最主要的就是要组装抽象的语法树。

先来看看如何使用解释器获取单个元素的值。示例代码如下:

1f5ce8b78aff4d6fab5c04f46435eeff

把前面定义的xml取名叫作“InterpreterTest.xml”,放到当前工程的根下面,运行看看,能正确获取值吗?运行结果如下:

3390e76ca86d4b81a9352e18fdb8c785

再来测试一下获取单个元素的属性的值。示例代码如下:

200f58091cf447b1b3c6b8bf18343e58

2f19e916bcac4e4fb62f62bdd919f6ed

运行结果如下:

e62a40be24c94580abb72a662f976b4b

就像前面讲述的那样,制定一种简单的语言,让客户端用来表达从xml中取值的表达式的语言,然后为它们定义一种文法的表示,也就是语法规则,然后用解释器对象来表示那些表达式,接下来通过运行解释器来解释并执行这些功能。

但是从前面的示例中,我们只能看到客户端直接使用解释器对象,来表示客户要从xml中取什么值的语法树,而没有看到如何从语言的表达式转换成为这种解释器的表示,这个功能是属于解析器的功能,没有划分在标准的解释器模式中,所以这里就先不演示,在后面将会有示例来介绍解析器。