解释器模式
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
关于解释器模式,听起来很陌生,其实我们大部分人都用过,比如正则表达式,以及一些任务调度都支持的Cron表达式。
举例:
Cron表达式(5 * 10-22 * * ? 从早上10点到晚上十点,每分钟的第5秒执行一次)
正则表达式(^[0-9]*$ 数字)
那么计算机是如何识别这些表达式的呢?就如同解释器模式的定义说的那样,先定义一种文法(语法规则),不管这套文法是简单还是复杂,必须有这些规则,然后根据这些规则将一个表达式构建成抽象语法树(下划线部分通常是由解析器来实现,而解析器并不属于解释器模式),构建出抽象语法树之后,再执行每个节点所对应的功能,这样就是所谓的计算机识别出了表达式。
解释器模式使用解释器对象来表示和处理相应的语法规则,一般一个解释器处理一条语法规则。理论上说,只要能用解释器对象把符合语法的表达式表示出来然后构成抽象的语法树(下划线部分就是所谓的将表达式构建成抽象语法树),那都可以用解释器模式来处理。
正是由于采用一个解释器对象负责一条语法规则的方式,使得扩展新的语法非常容易。扩展了新的语法,只要创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。
但是如果语法特别复杂,构建解释器模式需要的抽象语法树的工作是非常艰巨的,一个人解释器对象就是一个类,对应一条语法规则,因此包含许多规则的文法可能难以管理和维护。所以解释器模式不太适合于复杂的语法,对于复杂的语法,使用语法分析器或编译器生成器会更好一些。
我们来假设读取XML这么一个场景:
<?xml version="1.0" encoding="UTF-8"?> <root id="rootId"> <a> <b> <c name="testc">12345</c> <d id="1">d1</d> <d id="2">d2</d> <d id="3">d3</d> <d id="4">d4</d> </b> </a> </root>
对于抽象语法树这个树状结构,很明显可以使用组合模式来构建。解释器模式把需要解释的对象分成两大类,一类是节点元素,就是可以包含其他元素的组合元素,解释器模式里称做非终结符元素,对应为组合模式的Composite;另一类是终结符元素,相当于组合模式的叶子对象。解释整个抽象语法树的过程,也就是执行相应对象的功能的过程。
比如上面的xml,对应成抽象语法树,可能的结构如下:
接下来我们来看使用解释器模式实现读取上述XML功能的类图:
1.定义抽象解释器(ReadXmlAbstractExpression):用来约束所有被解释的语法对象,也就是节点元素和终结符元素都要实现的功能。
2.定义上下文(Context):用来封装解释器需要的一些全局数据,也可以在其中封装一些解释器的公共功能,相当于各个解释器的公共对象。
3.定义非终结符解释器(ElementExpression):这个元素相当于组合模式中的Composite对象,因此需要对子元素进行维护,另外这元素的解释处理,只需要把自己找到,作为下一个元素的父元素就可以了。
4.定义元素终结符解释器(ElementTerminalExpression):元素终结符解释器的解释处理就是要获取元素的值。
5.定义属性终结符解释器(PropertyTerminalExpression):属性终结符解释器的解释处理就是要获取元素相应的属性值。
最后我们来看看客户端如何来使用解释器:
public static void main(string[] args) { //准备上下文 Context context=new Context("InterpreterTest.xml"); //我们定义的文法表达式"root/a/b/c",表示根据路径获取c元素的值 //我们根据文法及表达式来构建解释器的抽象语法树 ElementExpression root=new ElementExpression("root"); ElementExpression a=new ElementExpression("a"); ElementExpression b=new ElementExpression("b"); ElementTerminalExpression c=new ElementTerminalExpression("c"); //组合起来 root.addEle(a); a.addEle(b); b.addEle(c); //开始解释 string ss[]=root.interpret(context); system.out.println("c的值是="+ss[0]); }
我们看到是在客户端实现的构建抽象语法树,这样工作量很大,尤其xml结构一变,基本要重新构建,可以用我们前面提到的解析器来构建抽象语法树:
public static void main(string[] args) { //准备上下文 Context context=new Context("InterpreterTest.xml"); //通过解析器来获取抽象语法树 ReadXmlExpression re = Parser.parse("root/a/b/c"); //开始解释 string ss[]=re.interpret(context); system.out.println("c的值是="+ss[0]); }
解析器Parser的代码相对复杂,这里不详细介绍了。