神马是 "解释器模式"?
先翻开《GOF》看看 Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
在开篇之前还是要科普几个概念:抽象语法树:解释器模式并未解释如何创建一个抽象语法树。它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来完成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接 client 提供。
解析器:指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序。
解释器:指的是解释抽象语法树,并执行每个节点对应的功能的程序。
要使用解释器模式,一个重要的前提就是要定义一套语法规则,也称为文法。不管这套文法的规则是简单还是复杂,必须要有这些规则,因为解释器模式就是按照这些规则来进行解析并执行相应的功能的。
先来看看解释器模式的结构图和说明:
AbstractExpression:定义解释器的接口,约定解释器的解释操作。TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。NonterminalExpression:非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的组合对象。可以有多种非终结符解释器。Context:上下文,通常包含各个解释器需要的数据或是公共的功能。Client:客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成为使用解释器对象描述的抽象语法树,然后调用解释操作。
下面我们通过一个 xml 示例来理解解释器模式:首先要为表达式设计简单的文法,为了通用,用 root 表示根元素,abc 等来代表元素,一个简单的 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>
约定表达式的文法如下:1. 获取单个元素的值:从根元素开始,一直到想要获取取值的元素,元素中间用 "/" 分隔,根元素前不加 "/"。比如,表达式 "root/a/b/c" 就表示获取根元素下,a 元素下,b 元素下,c 元素的值。2. 获取单个元素的属性的值:当然是多个,要获取值的属性一定是表达式的最后一个元素的属性,在最后一个元素后面添加 "." 然后再加上属性的名称。比如,表达式 "root/a/b/c.name" 就表示获取根元素下,a 元素下,b 元素下,c 元素的 name 属性的值。3. 获取相同元素名称的值,当然是多个,要获取值的元素一定是表达式的最后一个元素,在最后一个元素后面添加 "$"。比如,表达式 "root/a/b/d$" 就表示获取根元素下,a 元素下,b 元素下的多个 d 元素的值的集合。4. 获取相同元素名称的属性的值,当然也是多个:要获取属性值的元素一定是表达式的最后一个元素,在最后一个元素后面添加 "$"。比如,表达式 "root/a/b/d$.id$" 就表示获取根元素下,a 元素下,b 元素下的多个 d 元素的 id 属性的值的集合。
上面的 xml,对应的抽象语法树,可能的结构如图:
下面我们来看看具体的代码:1. 定义上下文:
- /**
- * 上下文,用来包含解释器需要的一些全局信息
- * @param {String} filePathName [需要读取的xml的路径和名字]
- */
- function Context(filePathName) {
- // 上一个被处理元素
- this.preEle = null;
- // xml的Document对象
- this.document = XmlUtil.getRoot(filePathName);
- }
Context.prototype = {// 重新初始化上下文 reInit: function () {this.preEle = null;}, /** * 各个 Expression 公共使用的方法 * 根据父元素和当前元素的名称来获取当前元素 * @param {Element} pEle [父元素] * @param {String} eleName [当前元素名称] * @return {Element|null} [找到的当前元素] */ getNowEle: function (pEle, eleName) { var tempNodeList = pEle.childNodes; var nowEle;
for (var i = 0, len = tempNodeList.length; i < len; i++) {if ((nowEle = tempNodeList[i]).nodeType === 1) if (nowEle.nodeName === eleName) return nowEle; }
return null; }, getPreEle: function () { return this.preEle;}, setPreEle: function (preEle) {this.preEle = preEle;}, getDocument: function () { return this.document;}};
在上下文中使用了一个工具对象 XmlUtil 来获取 xmlDom,下面我使用的是 DOM3 的 DOMPaser,某些浏览器可能不支持,请使用搞基浏览器:
- // 工具对象
- // 解析xml,获取相应的Document对象
- var XmlUtil = {
- getRoot: function (filePathName) {
- var parser = new DOMParser();
- var xmldom = parser.parseFromString('<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>', 'text/xml');
return xmldom; } };
下面就是解释器的代码:
- /**
- * 元素作为非终结符对应的解释器,解释并执行中间元素
- * @param {String} eleName [元素的名称]
- */
- function ElementExpression(eleName) {
- this.eles = [];
- this.eleName = eleName;
- }
ElementExpression.prototype = {addEle: function (eleName) {this.eles.push(eleName); return true; }, removeEle: function (ele) {for (var i = 0, len = this.eles.length; i < len; i++) {if (ele === this.eles[i]) this.eles.splice(i--, 1); } return true; }, interpret: function (context) {// 先取出上下文中的当前元素作为父级元素 // 查找到当前元素名称所对应的 xml 元素,并设置回到上下文中 var pEle = context.getPreEle();
if (!pEle) {// 说明现在获取的是根元素 context.setPreEle(context.getDocument().documentElement); } else {// 根据父级元素和要查找的元素的名称来获取当前的元素 var nowEle = context.getNowEle(pEle, this.eleName); // 把当前获取的元素放到上下文中 context.setPreEle(nowEle); }
var ss; // 循环调用子元素的 interpret 方法 for (var i = 0, len = this.eles.length; i < len; i++) {ss = this.eles[i].interpret(context); }
// 返回最后一个解释器的解释结果,一般最后一个解释器就是终结符解释器了 return ss; } };
/** * 元素作为终结符对应的解释器 * @param {String} name [元素的名称] */ function ElementTerminalExpression(name) {this.eleName = name;}
ElementTerminalExpression.prototype = {interpret: function (context) {var pEle = context.getPreEle(); var ele = null; if (!pEle) {ele = context.getDocument().documentElement; } else {ele = context.getNowEle(pEle, this.eleName); context.setPreEle(ele); }
// 获取元素的值 return ele.firstChild.nodeValue; } };
/** * 属性作为终结符对应的解释器 * @param {String} propName [属性的名称] */ function PropertyTerminalExpression(propName) {this.propName = propName;}
PropertyTerminalExpression.prototype = {interpret: function (context) {// 直接获取最后的元素属性的值 return context.getPreEle().getAttribute(this.propName); } };
来源: http://www.phperz.com/article/17/0425/275718.html