解释器模式

100 阅读2分钟

背景

在解释器(interpreter)模式中,程序要解决的问题会被用非常简单的“迷你语言”表述出来。即用“迷你语言”编写“迷你程序”把具体的问题表述出来。

迷你语言 无法单独工作,我们需要使用Java语言编写一个负责“翻译”的程序。

翻译程序会理解迷你语言,并解释和运行迷你程序,这段翻译程序称为翻译器,当需要解决的问题发送变化的时候,不需要修改Java语言程序,只需要修改迷你程序即可;

登场角色

AbstractExpression 抽象表达式

定义语法树节点的共同接口(API)

TerminalExpression 终结符表达式

TerminalExpression 对应于 BNF 的终结符表达式,如示例程序的 PrimitiveCommandndoe 类

NonterminalExpression 非终结符表达式

NonterminalExpression 对应于 BNF 的非终结符表达式,如示例程序的 CommandNode、RepeatCommand、CommandListNode 类

Context 文脉 上下文

Context 角色为解释器进行语法解析提供了必要的信息

Client 请求者

为了推导语法树,Client 角色会调用 TerminalExpression 角色 和 NonterminalExpression 角色

示例代码

基于 BNF 实现的表达式,底层解释器使用Java编写,递归的形式与组合模式有一定的类似

Node

public abstract class Node {
    public abstract void parse(Context context) throws ParseException;
}

ProgramNode

public class ProgramNode extends Node {

    private Node commandListNode;

    @Override
    public void parse(Context context) throws ParseException {
        // <program> ::= program <command list>
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    @Override
    public String toString() {
        return "[program " + commandListNode + "]";
    }
}

CommandListNode

public class CommandListNode extends Node {
    // <command list> ::= <command>* end

    private ArrayList<Node> list = new ArrayList<>();
    @Override
    public void parse(Context context) throws ParseException {
        while (true) {
            if (context.currentToken() == null) {
                throw new ParseException("Missing 'end'");
            } else if (context.currentToken().equals("end")) {
                context.skipToken("end");
                break;
            } else {
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                list.add(commandNode);
            }
        }
    }

    @Override
    public String toString() {
        return list.toString();
    }
}

CommandNode

public class CommandNode extends Node {

    private Node node;

    @Override
    public void parse(Context context) throws ParseException {
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }

    @Override
    public String toString() {
        return node.toString();
    }
}

PrimitiveCommandNode

public class PrimitiveCommandNode extends Node {

    private String name;

    @Override
    public void parse(Context context) throws ParseException {
        // <primitive command> ::= go | right | left

        name = context.currentToken();

        context.skipToken(name);
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new ParseException(name + " is undefined");
        }

    }

    @Override
    public String toString() {
        return name;
    }
}

RepeatCommandNode

public class RepeatCommandNode extends Node {

    private int number;

    private Node commandListNode;

    @Override
    public void parse(Context context) throws ParseException {

        // <repeat command> ::=repeat <number> <command list>
        context.skipToken("repeat");
        number = context.currentNumber();

        context.nextToken();

        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    @Override
    public String toString() {
        return "[repeat " + number + " " + commandListNode + "]";
    }
}

类图

功能分析

  1. 正则表达式。检索表达式,批处理语言等也可以基于interpreter模式实现;
  2. 必须时刻注意“进入这个方法时已经读到哪个标记了?出了这个方法时应该读到哪个标记?”;