解释器模式(Interpreter)2——跟着cc学设计系列

本文深入讲解了解释器模式的概念及其实现方式,包括定义、结构组成、示例代码等内容,并通过一个具体的XML解析示例展示了如何运用解释器模式来解决实际问题。
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)先看看抽象表达式的定义,非常简单,定义一个执行解释的方法,示例代码如下: /** * 抽象表达式 */ public abstract class AbstractExpression { /** * 解释的操作 * @param ctx 上下文对象 */ public abstract void interpret(Context ctx); } (2)再来看看终结符表达式的定义,示例代码如下: /** * 终结符表达式 */ public class TerminalExpression extends AbstractExpression{ public void interpret(Context ctx) { //实现与语法规则中的终结符相关联的解释操作 } } (3)接下来该看看非终结符表达式的定义了,示例代码如下: /** * 非终结符表达式 */ public class NonterminalExpression extends AbstractExpression{ public void interpret(Context ctx) { //实现与语法规则中的非终结符相关联的解释操作 } } (4)上下文的定义,示例代码如下: /** * 上下文,包含解释器之外的一些全局信息 */ public class Context { } (5)最后来看看客户端的定义,示例代码如下: /** * 使用解释器的客户 */ public class Client { //主要按照语法规则对特定的句子构建抽象语法树 //然后调用解释操作 } 看到这里,可能有些朋友会觉得,上面的示例代码里面什么都没有啊。这主要是因为解释器模式是跟具体的语法规则联系在一起的,没有相应的语法规则,自然写不出对应的处理代码来。 但是这些示例还是有意义的,可以通过它们看出解释器模式实现的基本架子,只是没有内部具体的处理罢了。 21.2.4 使用解释器模式重写示例 通过上面的讲述可以看出,要使用解释器模式,一个重要的前提就是要定义一套语法规则,也称为文法。不管这套文法的规则是简单还是复杂,必须有这么个东西,因为解释器模式就是来按照这些规则进行解析并执行相应的功能的。 1:为表达式设计简单的文法 为了通用,用root表示根元素,a、b、c、d等来代表元素,一个简单的xml如下: 12345 d1 d2 d3 d4 约定表达式的文法如下: 获取单个元素的值:从根元素开始,一直到想要获取值的元素,元素中间用“/”分隔,根元素前不加“/”。比如表达式“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)定义抽象的解释器 要实现解释器的功能,首先定义一个抽象的解释器,来约束所有被解释的语法对象,也就是节点元素和终结符元素都要实现的功能。示例代码如下: /** * 用于处理自定义Xml取值表达式的接口 */ public abstract class ReadXmlExpression { /** * 解释表达式 * @param c 上下文 * @return 解析过后的值,为了通用,可能是单个值,也可能是多个值, * 因此就返回一个数组 */ public abstract String[] interpret(Context c); } (2)定义上下文 上下文是用来封装解释器需要的一些全局数据,也可以在里面封装一些解释器的公共功能,可以相当于各个解释器的公共对象,示例代码如下: /** * 上下文,用来包含解释器需要的一些全局信息 */ public class Context { /** * 上一个被处理的元素 */ private Element preEle = null; /** * Dom解析Xml的Document对象 */ private Document document = null; /** * 构造方法 * @param filePathName 需要读取的xml的路径和名字 * @throws Exception */ public Context(String filePathName) throws Exception{ //通过辅助的Xml工具类来获取被解析的xml对应的Document对象 this.document = XmlUtil.getRoot(filePathName); } /** * 重新初始化上下文 */ public void reInit(){ preEle = null; } /** * 各个Expression公共使用的方法, * 根据父元素和当前元素的名称来获取当前的元素 * @param pEle 父元素 * @param eleName 当前元素的名称 * @return 找到的当前元素 */ public Element getNowEle(Element pEle,String eleName){ NodeList tempNodeList = pEle.getChildNodes(); for(int i=0;i

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26715458/viewspace-1070987/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/26715458/viewspace-1070987/

在IDEA的终端中运行 `npm run dev` 时出现 `'npm is not recognized'` 错误,通常意味着系统无法找到 `npm` 命令。以下是可能的原因及解决方法: ### 1. Node.js 和 npm 未正确安装 确保已经正确安装了 Node.js 和 npm。可以通过在终端中输入以下命令来检查是否安装成功: ```bash node -v npm -v ``` 如果这两个命令没有输出版本号,或者提示命令未找到,则需要安装 Node.js。可以从 [Node.js 官网](https://nodejs.org/)下载并安装合适的版本(推荐使用 LTS 版本)[^4]。 ### 2. npm 环境变量未配置 即使安装了 Node.js 和 npm,如果系统环境变量未正确配置,IDEA 的终端也可能无法识别 `npm` 命令。请检查系统路径(`PATH`)是否包含 Node.js 的安装目录下的 `npm` 和 `node` 可执行文件路径。 例如,在 Windows 上,默认的安装路径可能是: ``` C:\Program Files\nodejs\ ``` 确保该路径已添加到系统的 `PATH` 环境变量中。 ### 3. 使用不同终端或 shell 有时 IDEA 使用的终端可能与系统默认的终端不同,或者使用的 shell 不一致。可以尝试在系统自带的命令行工具(如 Windows 的 CMD 或 PowerShell)中运行 `npm run dev`,看是否能正常执行。如果系统终端可以运行而 IDEA 终端不行,则可能是 IDEA 的终端配置问题。 可以在 IDEA 的设置中更改终端使用的 shell: 1. 打开 **Settings (Preferences)**。 2. 进入 **Tools > Terminal**。 3. 修改 **Shell path** 为 `cmd.exe` 或 `powershell.exe`(Windows)或 `/bin/bash`(macOS/Linux)。 ### 4. 检查 IDEA 是否使用正确的环境 在某些情况下,IDEA 可能使用了隔离的环境(如通过 SDK 设置),导致无法访问全局安装的 npm 包。确保项目使用的 Node.js 解释器路径正确,可以在设置中检查: 1. 打开 **Settings (Preferences)**。 2. 进入 **Languages & Frameworks > Node.js and NPM**。 3. 确保 **Node interpreter** 指向正确的 `node` 可执行文件路径。 ### 5. 重新安装 npm 包 如果项目依赖未正确安装,也可能导致命令执行失败。可以尝试删除 `node_modules` 和 `package-lock.json` 后重新安装依赖: ```bash rm -rf node_modules package-lock.json npm install ``` ### 6. 检查权限问题(Windows) 在某些 Windows 系统上,权限或用户账户控制(UAC)设置可能导致 npm 命令无法执行。可以尝试以管理员身份运行 IDEA 或终端。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值