使用访问者模式给抽象语法树打印逆波兰式和彩虹括号

在上一篇文章中,为了给访问者模式做铺垫,我从零开始撸一个抽象语法树出来,今天我们用访问者模式给语法树整点活。

访问者模式

访问者模式是一种行为设计模式,它将算法与其所作用的对象隔离开来。通过这种方式,元素的执行算法可以随着访问者的改变而改变。对于Java而言,访问者模式为我们提供了双重分派的功能。

需求

访问者模式的概念说起来有点绕,我们先看看这次改造的需求。既然我们已经实现了语法树,那么有两个后续的需求:

  1. 打印当前表达式的逆波兰式
  2. 实现一下类似IDEA插件Rainbow Brackets的效果

由于我们那复杂而脆弱的语法解析器已经写好了,我实在是不想因为这样的需求而对语法解析器大改特改。所以,我们这次用访问者模式对语法解析器进行改造。

程序改造

定义访问者接口

首先,我们要定义一下访问者接口,因为我们一共有OperatorDataNodeStaticNodeFunctionNode这四种实际的节点,所以在访问者接口里也要体现这四种节点。

/**
 * 节点访问器
 */
public interface NodeVisitor {

    /**
     * 访问操作符
     * @param operator
     */
    void visitOperator(Operator operator);

    /**
     * 访问数据节点
     * @param dataNode
     */
    void visitDataNode(DataNode dataNode);

    /**
     * 访问常量节点
     * @param staticNode
     */
    void visitStaticNode(StaticNode staticNode);

    /**
     * 访问函数节点
     * @param functionNode
     */
    void visitFunctionNode(FunctionNode functionNode);
}

对节点进行少量的改造

在Node接口中多了这样的一个方法用来接收访问者。
在这里插入图片描述
然后,我们在不同的节点中都实现一下这个接口。

  1. 操作符节点
    在这里插入图片描述
  2. 变量节点
    在这里插入图片描述
  3. 数据节点
    在这里插入图片描述
  4. 函数节点
    在这里插入图片描述

需要注意的是,每个节点在accept访问者之后,都只会调用访问者访问对应节点的方法。比如StaticNode调用的是访问者的visitStaticNodeFunctionNode调用的是访问者的visitFunctionNode

实现Rainbow Brackets

public class PrettyPrintVisitor implements NodeVisitor{

    private static final int[] COLORS = new int[]{31,32,33,34,35};

    private int index=0;

    private void colorLeftBrackets(){
        System.out.print((char)27+"["+ COLORS[index%5]+"m("+(char) 27+"[0m");
        index++;
    }

    private void colorRightBrackets(){
        index--;
        System.out.print((char)27+"["+ COLORS[index%5]+"m)"+(char) 27+"[0m");
    }

    @Override
    public void visitOperator(Operator operator) {
        Node leftNode = operator.getLeftNode();
        int currentPriority = operator.priority();
        boolean showBrackets = false;
        if(leftNode instanceof Operator){
            if (((Operator) leftNode).priority()< currentPriority) {
                colorLeftBrackets();
                showBrackets = true;
            }
        }
        leftNode.accept(this);
        if (showBrackets) {
            colorRightBrackets();
            showBrackets = false;
        }
        System.out.print(" "+operator.operator()+" ");
        Node rightNode = operator.getRightNode();
        if(rightNode instanceof Operator){
            if (((Operator) rightNode).priority()<= currentPriority) {
                colorLeftBrackets();
                showBrackets = true;
            }
        }
        rightNode.accept(this);
        if (showBrackets) {
            colorRightBrackets();
        }
    }

    @Override
    public void visitDataNode(DataNode dataNode) {
        System.out.print(dataNode.getText());
    }

    @Override
    public void visitStaticNode(StaticNode staticNode) {
        System.out.print(staticNode.getText());
    }

    @Override
    public void visitFunctionNode(FunctionNode functionNode) {
        System.out.print(functionNode.getFuncName());
        colorLeftBrackets();
        for (int i = 0; i < functionNode.getParams().size(); i++) {
            functionNode.getParams().get(i).accept(this);
           if(i<functionNode.getParams().size()-1){
               System.out.print(",");
           }
        }
        colorRightBrackets();
    }
}

实现逆波兰式打印

public class ReversePolishVisitor implements NodeVisitor {
    @Override
    public void visitOperator(Operator operator) {
        operator.getLeftNode().accept(this);
        operator.getRightNode().accept(this);
        System.out.print(operator.getText());
    }

    @Override
    public void visitDataNode(DataNode dataNode) {
        System.out.print(dataNode.getText());
    }

    @Override
    public void visitStaticNode(StaticNode staticNode) {
        System.out.print(staticNode.getText());
    }

    @Override
    public void visitFunctionNode(FunctionNode functionNode) {
        System.out.print(functionNode.getText());
    }
}

Main

public class VisitorMain {
    public static void main(String[] args) {
        String exp = "(2-1)*3+2+(3*(9-(5+2)*1))";
        Node parse = Calculation.parse(exp);
        parse.accept(new PrettyPrintVisitor());
        System.out.println();
        String exp2 = "Max(8,Max(5,4))+plus100  (max(3,9))";
        Node parse2 = Calculation.parse(exp2);
        parse2.accept(new PrettyPrintVisitor());
        System.out.println();
        String exp3 = "2-(3-4)";
        Node parse3 = Calculation.parse(exp3);
        parse3.accept(new PrettyPrintVisitor());
        System.out.println();
        String exp4 = "(2-1)*3+2+(3*(9-(5+2)*1))";
        Node parse4 = Calculation.parse(exp4);
        parse4.accept(new ReversePolishVisitor());
        System.out.println();
    }
}

运行结果

从运行结果上看,Rainbow Brackets和逆波兰式都成功了
在这里插入图片描述

思考

  1. 访问者模式本身不是一个大众化的设计模式,应用场景相对较窄。
  2. 如果Node的类型多了一个,那NodeVisitor中的方法也得对应多一个。所以访问者模式适用于结构不怎么会发生变化的场景,比如上面这个语法树的场景。
  3. 访问者模式遵循开闭原则单一职责原则,但本身也会变得比较复杂。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值