《一天一模式》— 解释器模式

1、解释器模式的概念

给定一个语言,定义一个文法的一种表示, 并定义一个解释器, 这个解释器使用该表示来解释语言中的句子。 java

2、何时使用解释器模式

解释器模式是23种设计模式中惟一一种处理语法解析的设计模式。node

当须要求是,须要解释某种语法来执行业务,而且你可将该语言中的句子表示为一个抽象语法树,可使用解释器模式。设计模式

向《图解设计模式》里在解释器模式这章节提到的例子,使用BNF式描述语法,例子是经过一种语法来移动小车:bash

<program> ::= program <command list>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left

这种语法就能够作成抽象语法树,好比要控制小车走路,能够输入语法:program repeat 3 go right end end。框架

意思为重复执行3次go和right。工具

下面看看如何使用Java程序实现解释器模式的。性能

3、怎么使用解释器模式

3.1 实现方式

// 语法树节点的抽象类
public abstract class Node {

    abstract void parse(Context context);

}

// <program> ::= program <command list>
public class ProgramNode extends Node {

    private Node commandListNode;

    void parse(Context context) {
        // 解析程序命令,必须是program才能够向下执行
        // 等于从命令中跳过program
        context.skipToken("program");
        // 将后面的命令交给<command list>去解析
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

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

}

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

    ArrayList list = new ArrayList();

    void parse(Context context) {
        // 命令是由<command>* end组成的,因此要循环把*都解析,直到end
        while (true) {
            if (context.currentToken() == null) { // 缺乏end报错
                throw new IllegalArgumentException("missing end");
            } else if (context.currentToken().equals("end")) { // 解析end,跳出循环
                context.skipToken("end");
                break;
            } else { // 将命令交给<command>解析
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                // 便于打印结果定义了一个集合存放命令
                list.add(commandNode);
            }
        }
    }

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

}

// <command> ::= <repeat command> | <primitive command>
public class CommandNode extends Node {

    private Node node;

    void parse(Context context) {
        // 判断是<repeat command> 仍是 <primitive command>,而后找到想对应的解析对象
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }

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

}

// <repeat command> ::= repeat <number> <command list>
public class RepeatCommandNode extends Node {

    int number;
    private Node commandListNode;

    void parse(Context context) {
        // 解析repeat,在得到number用于打印,而后把number也解析(用的nextToken)
        context.skipToken("repeat");
        number = context.currentNumber();
        context.nextToken();
        // 接着将命令交给<command list>
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

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

}

// <primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node {

    String name;

    void parse(Context context) {
        // 解析这个命令
        name = context.currentToken();
        context.skipToken(name);
        // 若是不识别就报错
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new IllegalArgumentException("token is undefined.");
        }
    }

    public String toString() {
        return name;
    }

}

public class Context {

    StringTokenizer tokenizer;
    String currentToken;

    public Context(String text) {
        tokenizer = new StringTokenizer(text);
        nextToken();
    }

    public String nextToken() {
        if (tokenizer.hasMoreTokens()) {
            currentToken = tokenizer.nextToken();
        } else {
            currentToken = null;
        }
        return currentToken;
    }

    public String currentToken() {
        return currentToken;
    }

    public void skipToken(String token) {
        if (!token.equals(currentToken)) {
            throw new IllegalArgumentException("token is expected, but currentToken is found.");
        }
        nextToken();
    }

    public int currentNumber() {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("number is bad param.");
        }
        return number;
    }

}

使用方法以下:spa

public class Client {

    public static void main(String[] args) {
        String command5 = "program repeat 4 repeat 3 go right go right end right end end";
        Node node = new ProgramNode();
        node.parse(new Context(command5));
        System.out.println(node);
    }

}

// 输出
// [program [[repeat +4 [[repeat +3 [go, right, go, right]], right]]]]

3.2 解释器模式的好处

  • 能够很容易地改变和扩展方法, 由于该模式使用类来表示方法规则, 你可使用继承来改变或扩展该方法;
  • 也比较容易实现方法, 由于定义抽象语法树总各个节点的类的实现大致相似, 这些类都易于直接编写;
  • 解释器模式就是将一句话,转变为实际的命令程序执行而已。 而不用解释器模式自己也能够分析, 但经过继承抽象表达式的方式, 因为依赖转置原则, 使得文法的扩展和维护都带来的方便;

3.3 解释器模式须要注意的地方

解释器模式为方法中的每一条规则至少定义了一个类, 所以包含许多规则的方法可能难以管理和维护。 所以当方法很是复杂时, 使用其余的技术如 语法分析程序 或 编译器生成器来处理。设计

4、总结

解释器模式的关键在于如何将需求抽象成语法树。code

其业务实现很简单,用类来解析每一个命令。

解释器模式在实际的系统开发中使用的很是少,由于它会引发效率、性能以及维护等问题,通常在大中型的框架型项目可以找到它的身影,好比一些数据分析工具、报表设计工具、科学计算工具等等

以上就是我对解释器模式的一些理解,有不足之处请你们指出,谢谢。

相关文章
相关标签/搜索