解释器模式
模式定义
给定一个语言,定义它的文法(语法规则)的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在GOF95中解释器模式被分类为行为型模式。
模式结构与说明
- AbstractionExpression:定义了解释器的接口,约定解释器的解释操作
- TerminalExpression:端点解释器,用来实现语法规则中和端点相关的操作,不包含其它解释器。如果使用组合模式构建语法树的话,就相当于Leaf角色
- NonterminalExpression:非端点解释器,用来实现语法规则中非端点相关的操作,通常对应了一个语法规则,可以包含其它解释器。如果使用组合模式构建语法树的话,就相当于Composite角色。非端点解释器可以有多种
- Context:上下文,包含各种解释器需要的数据或者公共功能。上下文在解释器模式中非常重要,它会被传递到所有解释器中,解释器可以在上下文中存取数据
- Client:解释器的客户端
理解解释器模式的思路,需要首先了解两个重要的概念:
- 解析器(Parser):能够把Client的表达式请求解析为抽象语法树形式
- 解释器(Interpreter):能够解释抽象语法树,并执行每个节点对应的功能
那么,解析器这个角色谁来承担呢?亦即,谁来构建语法树?如果语法很简单,可以让客户端来手工构建,否则,应当开发单独的解析器,能够把字符串形式的表达式转换为语法树。
解释器模式的优点:
- 将每个语法规则表示成一个类,方便实现语言
- 由于语法由许多类表示,因此可以轻易的改变或者扩展此语言
- 通过在类结构中加入新的方法,可以在解释的同时添加新的行为,例如验证、打印格式美化
解释器模式的缺点:
- 当语法规则很复杂时,该模式会变得异常复杂
解释器模式的适用场景:
- 当需要实现一个简单的语言时
- 有一个简单的语法,并且简单比效率更加重要
应用举例
考虑一个简单的XPath解释器,支持以下形式的XPath语法:
1 2 3 4 |
#选择元素根元素msg下body子元素的el子元素 /msg/body/el #选择元素根元素msg下body子元素的el子元素的name属性 /msg/body/el/@name |
分析一下可以看到,XML元素可以作为非端点元素或者端点元素,XML属性则只能支持端点元素,因此,我们设计如下的解释器类层次:
XPath解释器的实现思路是:
- 通过Dom4j等工具执行XML解析
- 从根XPathExpression开始解析
- 如果当前是非端点表达式,那么在Context中设置parent为当前元素
- 如果当前是端点表达式,那么:
- 对于元素端点,依据parent获取当前元素,进而获取元素的文本
- 对于属性端点,依据parent获取对应属性,进而获取元素的属性
主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
/** * XPath解析上下文 * */ public class XPathContext { /* 上下文中包含公共的数据 */ private Document doc; //XML文档 private Element parent; //当前父元素 /* 上下文中可以包含公共的功能 */ public XPathContext( InputStream xml ) throws DocumentException { this.doc = new SAXReader().read( xml ); } public void setParent( String name ) { if ( parent == null ) parent = doc.getRootElement(); else parent = parent.element( name ); } public String getElementText( String name ) { return parent.elementText( name ); } public String getAttribute( String name ) { return parent.attributeValue( name ); } } /** * XPath表达式超类 * */ public abstract class XPathExpression { private String name; public XPathExpression( String name ) { this.name = name; } public abstract String interpret( XPathContext ctx ); public String getName() { return name; } } /** * XPath元素表达式,非端点 * */ public class XPathElementExpression extends XPathExpression { public XPathElementExpression( String name ) { super( name ); } private List<XPathExpression> subExprs = new ArrayList<XPathExpression>(); @Override public String interpret( XPathContext ctx ) { ctx.setParent( getName() ); //只是在上下文中设置父元素 return subExprs.get( 0 ).interpret( ctx ); //然后就遍历子节点 } public void addSubExpression( XPathExpression expr ) { subExprs.add( expr ); } } /** * 元素端点 */ public class XPathTerminalElementExpression extends XPathExpression { public XPathTerminalElementExpression( String name ) { super( name ); } @Override public String interpret( XPathContext ctx ) { return ctx.getElementText( getName() ); } } /** * 属性端点 */ public class XPathTerminalAttributeExpression extends XPathExpression { public XPathTerminalAttributeExpression( String name ) { super( name ); } @Override public String interpret( XPathContext ctx ) { return ctx.getAttribute( getName() ); } } public class Client { public static void main( String[] args ) throws Throwable { InputStream is = new FileInputStream( "~/example.xml" ); XPathContext ctx = new XPathContext( is ); //调用解析器将文本解析为语法树 XPathExpression expr = parse( "/msg/body/el/@name" ); System.out.println( expr.interpret( ctx ) ); //打印属性值 is.close(); } private static XPathExpression parse( String string ) { return null; } } |
经典应用
Java中的正则式
java.util.regex.Pattern包含了大量的内部类,组成了一个解释器层次:
Node就相当于解释器模式中的Interpreter角色,在执行正则式匹配的时候,Pattern会调用Node的match方法,该方法相当于解释器模式中的interpret(),会调用下一个节点的match(),与标准的解释器模式类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static class Start extends Node { int minLength; Start(Node node) { this.next = node; //下一个节点 minLength = info.minLength; } boolean match(Matcher matcher, int i, CharSequence seq) { ... boolean ret = false; int guard = matcher.to - minLength; for (; i <= guard; i++) { if (ret = next.match(matcher, i, seq)) //调用下一个节点的match() break; if (i == guard) matcher.hitEnd = true; } ... } } |
模式演变
- 与组合模式联用:由于涉及到语法树,解释器模式通常都要和组合模式一起使用,TerminalExpression充当Leaf,NonTerminalExpression充当Composite
- 与迭代器模式联用:遍历语法树时,可以使用迭代器
Leave a Reply