您的位置:首页 > 编程语言 > Java开发

Java私塾:研磨设计模式 之 解释器模式(Interpreter)3

2013-12-11 09:59 549 查看


21.3 模式讲解


21.3.1 认识解释器模式

(1)解释器模式的功能

解释器模式使用解释器对象来表示和处理相应的语法规则,一般一个解释器处理一条语法规则。理论上来说,只要能用解释器对象把符合语法的表达式表示出来,而且能够构成抽象的语法树,那都可以使用解释器模式来处理。

(2)语法规则和解释器

语法规则和解释器之间是有对应关系的,一般一个解释器处理一条语法规则,但是反过来并不成立,一条语法规则是可以有多种解释和处理的,也就是一条语法规则可以对应多个解释器对象。

(3)上下文的公用性

上下文在解释器模式中起到非常重要的作用,由于上下文会被传递到所有的解释器中,因此可以在上下文中存储和访问解释器的状态,比如前面的解释器可以存储一些数据在上下文中,后面的解释器就可以获取这些值。

另外还可以通过上下文传递一些在解释器外部,但是解释器需要的数据,也可以是一些全局的,公共的数据。

上下文还有一个功能,就是可以提供所有解释器对象的公共功能,类似于对象组合,而不是使用继承来获取公共功能,在每个解释器对象里面都可以调用。

(4)谁来构建抽象语法树

在前面的示例中,大家已经发现,自己在客户端手工来构建抽象语法树,是很麻烦的,但是在解释器模式中,并没有涉及这部分功能,只是负责对构建好的抽象语法树进行解释处理。前面的测试简单,所以手工构建抽象语法树也不是特别困难的事,要是复杂了呢?如果还是手工创建,那跟修改解析xml的代码也差不了多少。后面会给大家讲到,可以提供解析器来实现把表达式转换成为抽象语法树。

还有一个问题,就是一条语法规则是可以对应多个解释器对象的,也就是说同一个元素,是可以转换成多个解释器对象的,这也就意味着同样一个表达式,是可以构成不同的抽象语法树的,这也造成构建抽象语法树变得很困难,而且工作量很大。

(5)谁负责解释操作

只要定义好了抽象语法树,肯定是解释器来负责解释执行。虽然有不同的语法规则,但是解释器不负责选择究竟用哪一个解释器对象来解释执行语法规则,选择解释器的功能在构建抽象语法树的时候就完成了。

所以解释器只要忠实的按照抽象语法树解释执行就好了。

(6)解释器模式的调用顺序示意图

解释器模式的调用顺序如图21.4所示:



图21.4 解释器模式的调用顺序示意图


21.3.2 读取多个元素或属性的值

前面看过了如何获取单个元素的值和单个元素的属性的值,下面应该来看看如何获取多个元素的值,还有多个元素中相同名称的属性的值了。

获取多个值和前面获取单个值的实现思路大致相同,只是在取值的时候需要循环整个NodelList,依次取值,而不是只取出第一个来。当然,由于语法发生了变动,所以对应的解释器也需要发生改变。

首先是有了一个表示多个元素作为终结符的语法,比如“root/a/b/d$”中的“d$”;其次有了一个表示多个元素的属性作为终结符的语法,比如“root/a/b/d$.id$”中的“.id$”;最后还有一个表示多个元素,但不是终结符的语法,比如“root/a/b/d$.id$”中的“d$”。

还是看看代码示例吧,会比较清楚。

(1)解释器接口没有变化,原本就定义的是数组,早做好准备了。

(2)读取Xml的工具类XmlUtil也没有任何变化

(3)上下文做了一点改变:

把原来用来记录上一次操作的元素,变成记录上一次操作的多个元素的这么一个集合,然后为它提供相应的getter/setter方法
另外,原来根据父元素和当前元素的名称获取当前元素的方法,变成了根据父元素和当前元素的名称来获取多个元素
重新初始化上下文的方法里面,初始化的就是记录上一次操作的多个元素的这个集合了

具体的Context类的代码示例如下:
/**

* 上下文,用来包含解释器需要的一些全局信息

*/

public class Context {

/**

* Dom解析Xml的Document对象

*/

private Document document = null;

/**

* 上一次被处理的多个元素

*/

private List<Element> preEles = new ArrayList<Element>();

/**

* 构造方法

* @param filePathName 需要读取的xml的路径和名字

* @throws Exception

*/

public Context(String filePathName) throws Exception{

//通过辅助的Xml工具类来获取被解析的xml对应的Document对象

this.document = XmlUtil.getRoot(filePathName);

}

/**

* 重新初始化上下文

*/

public void reInit(){

preEles = new ArrayList<Element>();

}

/**

* 各个Expression公共使用的方法,

* 根据父元素和当前元素的名称来获取当前的多个元素的集合

* @param pEle 父元素

* @param eleName 当前元素的名称

* @return 当前的多个元素的集合

*/

public List<Element> getNowEles(Element pEle,String eleName){

List<Element> elements = new ArrayList<Element>();

NodeList tempNodeList = pEle.getChildNodes();

for(int i=0;i<tempNodeList.getLength();i++){

if(tempNodeList.item(i) instanceof Element){

Element nowEle = (Element)tempNodeList.item(i);

if(nowEle.getTagName().equals(eleName)){

elements.add(nowEle);

}

}

}

return elements;

}

public Document getDocument() {

return document;

}

public List<Element> getPreEles() {

return preEles;

}

public void setPreEles(List<Element> nowEles) {

this.preEles = nowEles;

}

}
(4)处理单个非终结符的对象ElementExpression,跟以前处理单个元素相比,主要是现在需要面向多个父元素,但是由于是单个非终结符的处理,因此在多个父元素下面去查找符合要求的元素,找到一个就停止,示例代码如下:
/**

* 单个元素作为非终结符的解释器

*/

public class ElementExpression extends ReadXmlExpression{

/**

* 用来记录组合的ReadXmlExpression元素

*/

private Collection<ReadXmlExpression> eles =

new ArrayList<ReadXmlExpression>();

/**

* 元素的名称

*/

private String eleName = "";

public ElementExpression(String eleName){

this.eleName = eleName;

}

public boolean addEle(ReadXmlExpression ele){

this.eles.add(ele);

return true;

}

public boolean removeEle(ReadXmlExpression ele){

this.eles.remove(ele);

return true;

}

public String[] interpret(Context c) {

//先取出上下文里的父级元素

List<Element> pEles = c.getPreEles();

Element ele = null;

//把当前获取的元素放到上下文里面

List<Element> nowEles = new ArrayList<Element>();

if(pEles.size()==0){

//说明现在获取的是根元素

ele = c.getDocument().getDocumentElement();

pEles.add(ele);

c.setPreEles(pEles);

}else{

for(Element tempEle : pEles){

nowEles.addAll(c.getNowEles(tempEle, eleName));

if(nowEles.size()>0){

//找到一个就停止

break;

}

}

List<Element> tempList = new ArrayList<Element>();

tempList.add(nowEles.get(0));

c.setPreEles(tempList);

}

//循环调用子元素的interpret方法

String [] ss = null;

for(ReadXmlExpression tempEle : eles){

ss = tempEle.interpret(c);

}

return ss;

}

}
(5)用来处理单个元素作为终结符的类,也发生了一点改变,主要是从多个父元素去获取当前元素,如果当前元素是多个,就取第一个,示例代码如下:
/**

* 元素作为终结符对应的解释器

*/

public class ElementTerminalExpression extends ReadXmlExpression{

/**

* 元素的名字

*/

private String eleName = "";

public ElementTerminalExpression(String name){

this.eleName = name;

}

public String[] interpret(Context c) {

//先取出上下文里的当前元素作为父级元素

List<Element> pEles = c.getPreEles();

//查找到当前元素名称所对应的xml元素

Element ele = null;

if(pEles.size() == 0){

//说明现在获取的是根元素

ele = c.getDocument().getDocumentElement();

}else{

//获取当前的元素

ele = c.getNowEles(pEles.get(0), eleName).get(0);

}

//然后需要去获取这个元素的值

String[] ss = new String[1];

ss[0] = ele.getFirstChild().getNodeValue();

return ss;

}

}
(6)新添加一个解释器,用来解释处理以多个元素的属性作为终结符的情况,它的实现比较简单,只要获取到最后的多个元素对象,然后循环这些元素,一个一个取出相应的属性值就好了,示例代码如下:
/**

* 以多个元素的属性做为终结符的解释处理对象

*/

public class PropertysTerminalExpression

extends ReadXmlExpression{

/**

* 属性名字

*/

private String propName;

public PropertysTerminalExpression(String propName){

this.propName = propName;

}

public String[] interpret(Context c) {

//获取最后的多个元素

List<Element> eles = c.getPreEles();

String[] ss = new String[eles.size()];

//循环多个元素,获取每个的属性的值

for(int i=0;i<ss.length;i++){

ss[i] = eles.get(i).getAttribute(this.propName);

}

return ss;

}

}
(7)新添加一个解释器,用来解释处理以多个元素作为终结符的情况,示例代码如下:
/**

* 以多个元素作为终结符的解释处理对象

*/

public class ElementsTerminalExpression extends

ReadXmlExpression{

/**

* 元素的名称

*/

private String eleName = "";

public ElementsTerminalExpression(String name){

this.eleName = name;

}

public String[] interpret(Context c) {

//先取出上下文里的父级元素

List<Element> pEles = c.getPreEles();

//获取当前的多个元素

List<Element> nowEles = new ArrayList<Element>();

for(Element ele : pEles){

nowEles.addAll(c.getNowEles(ele, eleName));

}

//然后需要去获取这些元素的值

String[] ss = new String[nowEles.size()];

for(int i=0;i<ss.length;i++){

ss[i] = nowEles.get(i).getFirstChild().getNodeValue();

}

return ss;

}

}
(8)新添加一个解释器,用来解释处理以多个元素作为非终结符的情况,它的实现类似于以单个元素作为非终结符的情况,只是这次处理的是多个,需要循环处理,同样需要维护子对象,在我们现在设计的语法中,多个元素后面是可以再加子元素的,最起码可以加多个属性的终结符对象,示例代码如下:
/**

* 多个元素做为非终结符的解释处理对象

*/

public class ElementsExpression extends ReadXmlExpression{

/**

* 用来记录组合的ReadXmlExpression元素

*/

private Collection<ReadXmlExpression> eles =

new ArrayList<ReadXmlExpression>();

/**

* 元素名字

*/

private String eleName = "";

public ElementsExpression(String eleName){

this.eleName = eleName;

}

public String[] interpret(Context c) {

//先取出上下文里的父级元素

List<Element> pEles = c.getPreEles();

//把当前获取的元素放到上下文里面,这次是获取多个元素

List<Element> nowEles = new ArrayList<Element>();

for(Element ele : pEles){

nowEles.addAll(c.getNowEles(ele, eleName));

}

c.setPreEles(nowEles);

//循环调用子元素的interpret方法

String [] ss = null;

for(ReadXmlExpression ele : eles){

ss = ele.interpret(c);

}

return ss;

}

public boolean addEle(ReadXmlExpression ele){

this.eles.add(ele);

return true;

}

public boolean removeEle(ReadXmlExpression ele){

this.eles.remove(ele);

return true;

}

}
(9)终于可以写客户端来测试一下了,看看是否能实现期望的功能。先测试获取多个元素的值的情况,示例代码如下:
public class Client {

public static void main(String[] args) throws Exception {

//准备上下文

Context c = new Context("InterpreterTest.xml");

//想要获取多个d元素的值,也就是如下表达式的值:"root/a/b/d$"

//首先要构建解释器的抽象语法树

ElementExpression root = new ElementExpression("root");

ElementExpression aEle = new ElementExpression("a");

ElementExpression bEle = new ElementExpression("b");

ElementsTerminalExpression dEle =

new ElementsTerminalExpression("d");

//组合起来

root.addEle(aEle);

aEle.addEle(bEle);

bEle.addEle(dEle);

//调用

String ss[] = root.interpret(c);

for(String s : ss){

System.out.println("d的值是="+s);

}

}

}
测试结果如下:
d的值是=d1

d的值是=d2

d的值是=d3

d的值是=d4
接下来测试一下获取多个属性值的情况,示例代码如下:
public class Client {

public static void main(String[] args) throws Exception {

//准备上下文

Context c = new Context("InterpreterTest.xml");

//想要获取d元素的id属性,也就是如下表达式的值:"a/b/d$.id$"

//首先要构建解释器的抽象语法树

ElementExpression root = new ElementExpression("root");

ElementExpression aEle = new ElementExpression("a");

ElementExpression bEle = new ElementExpression("b");

ElementsExpression dEle = new ElementsExpression("d");

PropertysTerminalExpression prop =

new PropertysTerminalExpression("id");

//组合

root.addEle(aEle);

aEle.addEle(bEle);

bEle.addEle(dEle);

dEle.addEle(prop);

//调用

String ss[] = root.interpret(c);

for (String s : ss) {

System.out.println("d的属性id值是=" + s);

}

}

}
测试结果如下:
d的属性id值是=1

d的属性id值是=2

d的属性id值是=3

d的属性id值是=4
也很简单,是不是。只要学会了处理单个的值,处理多个值也就变得容易了,只要把原来获取单个值的地方改成循环操作即可。

当然,如果要使用同一个上下文,连续进行解析,是同样需要重新初始化上下文对象的。你还可以尝试一下,如果是想要获取多个元素下的,多个元素的同一个属性的值,能实现吗?你自己去测试,应该是可以实现的。


21.3.3 解析器

前面看完了解释器部分的功能,看到只要构建好了抽象语法树,解释器就能够正确地解释并执行它,但是该如何得到这个抽象语法树呢?前面的测试都是人工组合好抽象语法树的,如果实际开发中还这样做,基本上工作量跟修改解析xml的代码差不多。

这就需要解析器出场了,这个程序专门负责把按照语法表达的表达式,解析转换成为解释器需要的抽象语法树。当然解析器是跟表达式的语法,还有解释器对象紧密关联的。

下面来示例一下解析器的实现,把符合前面定义的语法的表达式,转换成为前面实现的解释器的抽象语法树。解析器有很多种实现方式,没有什么定式,只要能完成相应的功能即可,比如表驱动、语法分析生成程序等等。这里的示例采用自己来分解表达式以实现构建抽象语法树的功能,没有使用递归,是用循环实现的,当然也可以用递归来做。

(1)实现思路

要实现解析器也不复杂,大约有下面三个步骤:

第一步:把客户端传递来的表达式进行分解,分解成为一个一个的元素,并用一个对应的解析模型来封装这个元素的一些信息。

第二步:根据每个元素的信息,转化成相对应的解析器对象

第三步:按照先后顺序,把这些解析器对象组合起来,就得到抽象语法树了。

可能有朋友会说,为什么不把第一步和第二步合并,直接分解出一个元素就转换成相应的解析器对象呢?原因有两个:

其一是功能分离,不要让一个方法的功能过于复杂;
其二是为了今后的修改和扩展,现在语法简单,所以转换成解析器对象需要考虑的东西少,直接转换也不难,但要是语法复杂了,直接转换就很杂乱了。

事实上,封装解析属性的数据模型充当了第一步和第二步操作间的接口,使第一步和第二步都变简单了。

(2)先来看看用来封装每一个解析出来的元素对应的属性对象,示例代码如下:
/**

* 用来封装每一个解析出来的元素对应的属性

*/

public class ParserModel {

/**

* 是否单个值

*/

private boolean singleVlaue;

/**

* 是否属性,不是属性就是元素

*/

private boolean propertyValue;

/**

* 是否终结符

*/

private boolean end;

public boolean isEnd() {

return end;

}

public void setEnd(boolean end) {

this.end = end;

}

public boolean isSingleVlaue() {

return singleVlaue;

}

public void setSingleVlaue(boolean oneVlaue) {

this.singleVlaue = oneVlaue;

}

public boolean isPropertyValue() {

return propertyValue;

}

public void setPropertyValue(boolean propertyValue) {

this.propertyValue = propertyValue;

}

}
(3)看看解析器的实现,代码稍微复杂点,注释很详尽,为了整体展示解析器,就不去分开每步单讲了,不过要注意一点:下面这种实现没有考虑并发处理的情况,如果要用在多线程环境下,需要补充相应的处理,特别提示一下。示例代码如下:
/**

* 根据语法来解析表达式,转换成为相应的抽象语法树

*/

public class Parser {

/**

* 私有化构造器,避免外部无谓的创建对象实例

*/

private Parser(){

//

}

//定义几个常量,内部使用

private final static String BACKLASH = "/";

private final static String DOT = ".";

private final static String DOLLAR = "$";

/**

* 按照分解的先后记录需要解析的元素的名称

*/

private static List<String> listEle = null;

/**

* 传入一个字符串表达式,通过解析,组合成为一个抽象的语法树

* @param expr 描述要取值的字符串表达式

* @return 对应的抽象语法树

*/

public static ReadXmlExpression parse(String expr){

//先初始化记录需解析的元素的名称的集 会

listEle = new ArrayList<String>();

//第一步:分解表达式,得到需要解析的元素名称和该元素对应的解析模型

Map<String,ParserModel> mapPath = parseMapPath(expr);

//第二步:根据节点的属性转换成为相应的解释器对象

List<ReadXmlExpression> list = mapPath2Interpreter(

mapPath);

//第三步:组合抽象语法树,一定要按照先后顺序来组合,

//否则对象的包含关系就乱了

ReadXmlExpression returnRe = buildTree(list);

return returnRe;

}

/*----------------------开始实现第一步-----------------------*/

/**

* 按照从左到右顺序来分解表达式,得到需要解析的元素名称,

* 还有该元素对应的解析模型

* @param expr 需要分解的表达式

* @return 得到需要解析的元素名称,还有该元素对应的解析模型

*/

private static Map<String,ParserModel> parseMapPath(

String expr){

//先按照/分割字符串

StringTokenizer tokenizer = new StringTokenizer(

expr, BACKLASH);

//初始化一个map用来存放分解出来的值

Map<String,ParserModel> mapPath =

new HashMap<String,ParserModel>();

while (tokenizer.hasMoreTokens()) {

String onePath = tokenizer.nextToken();

if (tokenizer.hasMoreTokens()) {

//还有下一个值,说明这不是最后一个元素

//按照现在的语法,属性必然在最后,因此也不是属性

setParsePath(false,onePath,false,mapPath);

} else {

//说明到最后了

int dotIndex = onePath.indexOf(DOT);

if (dotIndex > 0) {

//说明是要获取属性的值,那就按照"."来分割,

//前面的就是元素名字,后面的是属性的名字

String eleName = onePath.substring(0, dotIndex);

String propName = onePath.substring(dotIndex + 1);

//设置属性前面的那个元素,自然不是最后一个,也不是属性

setParsePath(false,eleName,false,mapPath);

//设置属性,按照现在的语法定义,属性只能是最后一个

setParsePath(true,propName,true,mapPath);

} else {

//说明是取元素的值,而且是最后一个元素的值

setParsePath(true,onePath,false,mapPath);

}

break;

}

}

return mapPath;

}

/**

* 按照分解出来的位置和名称来设置需要解析的元素名称,

* 还有该元素对应的解析模型

* @param end 是否是最后一个

* @param ele 元素名称

* @param propertyValue 是否是取属性

* @param mapPath 设置需要解析的元素名称,还有该元素对应的解析模型的Map

*/

private static void setParsePath(boolean end,String ele

,boolean propertyValue,Map<String,ParserModel> mapPath){

ParserModel pm = new ParserModel();

pm.setEnd(end);

//如果带有$符号就说明不是一个值

pm.setSingleVlaue(!(ele.indexOf(DOLLAR)>0));

pm.setPropertyValue(propertyValue);

//去掉$

ele = ele.replace(DOLLAR, "");

mapPath.put(ele,pm);

listEle.add(ele);

}

/*----------------------第一步实现结束-----------------------*/

/*----------------------开始实现第二步-----------------------*/

/**

* 把分解出来的元素名称,根据对应的解析模型转换成为相应的解释器对象

* @param mapPath 分解出来的需解析的元素名称,还有该元素对应的解析模型

* @return 把每个元素转换成为相应的解释器对象后的集合

*/

private static List<ReadXmlExpression> mapPath2Interpreter(

Map<String,ParserModel> mapPath){

List<ReadXmlExpression> list =

new ArrayList<ReadXmlExpression>();

//一定要按照分解的先后顺序来转换成解释器对象

for(String key : listEle){

ParserModel pm = mapPath.get(key);

ReadXmlExpression obj = null;

if(!pm.isEnd()){

if(pm.isSingleVlaue()){

//不是最后一个,是一个值,转化为

obj = new ElementExpression(key);

}else{

//不是最后一个,是多个值,转化为

obj = new ElementsExpression(key);

}

}else{

if(pm.isPropertyValue()){

if(pm.isSingleVlaue()){

//是最后一个,是一个值,取属性的值,转化为

obj = new PropertyTerminalExpression(key);

}else{

//是最后一个,是多个值,取属性的值,转化为

obj = new PropertysTerminalExpression(key);

}

}else{

if(pm.isSingleVlaue()){

//是最后一个,是一个值,取元素的值,转化为

obj = new ElementTerminalExpression(key);

}else{

//是最后一个,是多个值,取元素的值,转化为

obj = new ElementsTerminalExpression(key);

}

}

}

//把转换后的对象添加到集合中

list.add(obj);

}

return list;

}

/*----------------------第二步实现结束-----------------------*/

/*----------------------开始实现第三步-----------------------*/

private static ReadXmlExpression buildTree(

List<ReadXmlExpression> list){

//第一个对象,也是返回去的对象,就是抽象语法树的根

ReadXmlExpression returnRe = null;

//定义上一个对象

ReadXmlExpression preRe = null;

for(ReadXmlExpression re : list){

if(preRe==null){

//说明是第一个元素

preRe = re;

returnRe = re;

}else{

//把元素添加到上一个对象下面,同时把本对象设置成为oldRe,

//作为下一个对象的父结点

if(preRe instanceof ElementExpression){

ElementExpression ele =

(ElementExpression)preRe;

ele.addEle(re);

preRe = re;

}else if(preRe instanceof ElementsExpression){

ElementsExpression eles =

(ElementsExpression)preRe;

eles.addEle(re);

preRe = re;

}

}

}

return returnRe;

}

/*----------------------第三步实现结束-----------------------*/

}
(4)看完这个稍长点的解析器程序,该来体会一下,有了它对我们的开发有什么好处,写个客户端来测试看看。现在的客户端就非常简单了,主要三步:

首先是设计好想要取值的表达式
然后是通过解析器解析获取抽象语法树
最后就是请求解释器解释并执行这个抽象语法树,就得到最后的结果了

客户端测试的示例代码如下:
public class Client {

public static void main(String[] args) throws Exception {

//准备上下文

Context c = new Context("InterpreterTest.xml");

//通过解析器获取抽象语法树

ReadXmlExpression re = Parser.parse("root/a/b/d$.id$");

//请求解析,获取返回值

String ss[] = re.interpret(c);

for (String s : ss) {

System.out.println("d的属性id值是=" + s);

}

//如果要使用同一个上下文,连续进行解析,需要重新初始化上下文对象

c.reInit();

ReadXmlExpression re2 = Parser.parse("root/a/b/d$");

//请求解析,获取返回值

String ss2[] = re2.interpret(c);

for (String s : ss2) {

System.out.println("d的值是=" + s);

}

}

}
简单多了吧!通过使用解释器模式,自行设计一种简单的语法,就可以用很简单的表达式来获取你想要的xml中的值了。有的朋友可能会想到XPath,没错,本章示例实现的功能就是类似于XPath的部分功能。

如果今后xml的结构要是发生了变化,或者是想要获取不同的值,基本上就是修改那个表达式而已,你可以试试看,能否完成前面实现过的功能。比如:

想要获取c元素的值,表达式为:“root/a/b/c”
想要获取c元素的name属性值,表达式为:“root/a/b/c.name”
想要获取d元素的值,表达式为:“root/a/b/d$”,获取d的属性上面已经测试了


21.3.4 解释器模式的优缺点

l 易于实现语法

在解释器模式中,一条语法规则用一个解释器对象来解释执行,对于解释器的实现来讲,功能就变得比较简单,只需要考虑这一条语法规则的实现就好了,其它的都不用管。

l 易于扩展新的语法

正是由于采用一个解释器对象负责一条语法规则的方式,使得扩展新的语法非常容易,扩展了新的语法,只需要创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。

l 不适合复杂的语法

如果语法特别复杂,构建解释器模式需要的抽象语法树的工作是非常艰巨的,再加上有可能会需要构建多个抽象语法树。所以解释器模式不太适合于复杂的语法,对于复杂的语法,使用语法分析程序或编译器生成器可能会更好。


21.3.5 思考解释器模式

1:解释器模式的本质

解释器模式的本质:分离实现,解释执行。

解释器模式通过一个解释器对象处理一个语法规则的方式,把复杂的功能分离开;然后选择需要被执行的功能,并把这些功能组合成为需要被解释执行的抽象语法树;然后再按照抽象语法树来解释执行,实现相应的功能。

认识这个本质对于识别和变形使用解释器模式是很有作用的。从表面上看,解释器模式是关注的我们平时不太用到的自定义语法的处理,但是从实质上看,解释器模式的思路仍然是分离、封装、简化,跟很多模式是一样的。

比如可以使用解释器模式模拟状态模式的功能。如果把解释器模式要处理的语法简化到只有一个状态标记,把解释器看成是对状态的处理对象,对同一个表示状态的语法,可以有很多不同的解释器,也就是有很多不同的处理状态的对象,然后在创建抽象语法树的时候,简化成根据状态的标记来创建相应的解释器,不用再构建树了。你看看这么简化下来,是不是可以用解释器模拟出状态模式的功能呢?

同理,解释器模式可以模拟实现策略模式的功能,装饰器模式的功能等等,尤其是模拟装饰器模式的功能,构建抽象语法树的过程,自然就对应成为组合装饰器的过程。

2:何时选用解释器模式

建议在如下情况中,选用解释器模式:

当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式。

在使用解释器模式的时候,还有两个特点需要考虑,一个是语法相对应该比较简单,太复杂的语法不合适使用解释器模式;另一个是效率要求不是很高,对效率要求很高的情况下,不适合使用解释器模式。


21.3.6 相关模式

l 解释器模式和组合模式

这两个模式可以组合使用。

通常解释器模式都会使用组合模式来实现,这样能够方便的构建抽象语法树。一般非终结符解释器就相当于组合模式中的组合对象,终结符解释器就相当于叶子对象。

l 解释器模式和迭代器模式

这两个模式可以组合使用。

由于解释器模式通常使用组合模式来实现,因此在遍历整个对象结构的时候,自然可以使用迭代器模式。

l 解释器模式和享元模式

这两个模式可以组合使用。

在使用解释器模式的时候,可能会造成多个细粒度对象,比如会有各种各样的终结符解释器,而这些终结符解释器对不同的表达式来说是一样的,是可以共用的,因此可以引入享元模式来共享这些对象。

l 解释器模式和访问者模式

这两个模式可以组合使用。

在解释器模式中,语法规则和解释器对象是有对应关系的。语法规则的变动意味着功能的变化,自然会导致使用不同的解释器对象;而且一个语法规则可以被不同的解释器解释执行。

因此在构建抽象语法树的时候,如果每个节点所对应的解释器对象是固定的,这就意味着这个节点对应的功能是固定的,那么就不得不根据需要来构建不同的抽象语法树。

为了让构建的抽象语法树较为通用,那就要求解释器的功能不要那么固定,要能很方便的改变解释器的功能,这个时候问题就变成了,如何能够很方便的更改树形结构中节点对象的功能了,访问者模式可以很好的实现这个功能。

---------------------------------------------------------------------------

私塾在线学习网原创内容 跟着cc学设计系列 之 研磨设计模式

研磨设计讨论群【252780326】

原创内容,转载请注明出处【http://sishuok.com/forum/blogPost/list/0/5667.html

---------------------------------------------------------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: