语法树可以理解成是一种数据结构,假如某些语句已经被解析成一棵语法树,那么接下来就是要对此语法树进行处理,但考虑到不将处理操作与数据结构混合在一块,我们需要一种方法将其分离。其实对于语法树的处理最典型的处理模式就是访问者模式,它能很好的将数据结构与处理分离,提供了很好的解耦作用,让我们可以在生成语法树的过程只需关注如何构建相关的数据结构,而在对语法树处理的时候只需关注处理的逻辑,是一种非常巧的设计模式,接下来通过一个简单的代码案例看看如何实现一个访问者模式。
①定义访问者操作方法接口,声明所有访问者的操作方法。
public interface Visitor{
public void visit(RootNode rootNode);
public void visit(CommentNode commentNode);
public void visit(PageNode pageNode);
public void visit(IncludeNode includeNode);
public void visit(TaglibNode taglibNode);
}
②定义接口提供访问入口,语法树的每个节点都必须要实现此方法。
public interface NodeElement{
public void accept(Visitor v);
}
③不同类型的Node实现NodeElement接口,稍微改下原来定义的Node类,包括RootNode、CommentNode、PageNode、IncludeNode、TaglibNode,都添加accept方法。
public class RootNode implements NodeElement{
public void accept(Visitor v){
v.visit(this);
}
}
public class CommentNode implements NodeElement{
public void accept(Visitor v){
v.visit(this);
}
}
...
④现在假设我要按顺序将语法树中的注释获取出来,那么我只需要实现一个获取注释的visitor,对于不同的处理逻辑只需实现不同的visitor即可,这里由于对其他类型的节点不进行处理,所以其他节点的visit方法留空即可。
public class CommentVisitor implements Visitor{
public List<String> getComments(rootNode){
List<String> comments = new ArrayList();
List<Node> nodes = rootNode.getNodes();
Iterator<Node> iter = nodes.iterator();
while (iter.hasNext()) {
Node n = iter.next();
n.accept(this);
}
return comments;
}
public void visit(RootNode rootNode){}
public void visit(CommentNode commentNode){
comments.add(commentNode.getText());
}
public void visit(PageNode pageNode){}
public void visit(IncludeNode includeNode){}
public void visit(TaglibNode taglibNode){}
}
⑤测试类。
public class Test{
public static void main(String[] args){
RootNode root = Parser.parse();
CommentVisitor cv = new CommentVisitor();
List<String> comments = cv.getComments();
}
}
通过上面一个简单的例子可以看出访问者模式将数据结构和处理逻辑很好地解耦出来了,这种模式很经常用在语法树的解析处理上,熟悉此模式有助于对编译过程的理解,JSP对语法的解析也是如此。