デザインパターン ~Interpreter~

設計

1. はじめに

GoFのデザインパターンにおける、Interpreterパターンについてまとめます。

2. Interpreterパターンとは

  • Interpreterという英単語は、通訳という意味になります。
  • Interpreterパターンは、何らかの形式で書かれたファイルの中身を、「通訳」の役目を果たすプログラムで解析・表現する方式です。
  • GoFのデザインパターンでは、振る舞いに関するデザインパターンに分類されます。

3. サンプルクラス図

Interpreter.PNG

4. サンプルプログラム

テキストファイルに書かれた言語を構文解析するプログラムです。 構文解析対象テキストで使う言語の文法ではBNF記法を使います。

<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>・・・トークン program の後にコマンドの列が続いたもの、になります。
  • <command list>・・・が0個以上繰り返したあとトークン end が続いたもの、になります。
  • <command>・・・繰り返しコマンドまたは基本コマンドのいずれか、になります。( | は or を表す)
  • <repeat command>・・・トークン repeat のあとに繰り返し回数が続き,さらにコマンドの列が続いたもの、になります。
  • <primitive command>・・・go または right または left 、になります。

4-1. Contextクラス

構文解析のための前後関係を表すクラスです。

import java.util.StringTokenizer;

public class Context {

    private StringTokenizer tokenizer;
    private 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) throws Exception {
        if (!token.equals(currentToken)) {
            throw new Exception("Warning: " + token + " is expected, but " + currentToken + " is found.");
        }
        nextToken();
    }

    public int currentNumber() throws Exception {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new Exception("Warning: " + e);
        }
        return number;
    }
}

4-2. Nodeクラス

構文木の「ノード」となるクラスです。

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

4-3. ProgramNodeクラス

に対応するクラスです。

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

    private Node commandListNode;

    public void parse(Context context) throws Exception {
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

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

4-4. CommandNodeクラス

に対応するクラスです。

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

    private Node node;

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

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

4-5. RepeatCommandNodeクラス

に対応するクラスです。

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

    private int number;
    private Node commandListNode;

    public void parse(Context context) throws Exception {
        context.skipToken("repeat");
        number = context.currentNumber();
        context.nextToken();
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

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

4-6. CommandListNodeクラス

に対応するクラスです。

import java.util.ArrayList;

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

    private ArrayList list = new ArrayList();

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

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

4-7. PrimitiveCommandNodeクラス

に対応するクラスです。

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

    private String name;

    public void parse(Context context) throws Exception {
        name = context.currentToken();
        context.skipToken(name);
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new Exception(name + " is undefined");
        }
    }

    public String toString() {
        return name;
    }
}

4-8. 構文解析対象テキスト

構文解析対象となるテキストです。

program end
program go end
program go right go right go right go right end
program repeat 4 go right end end
program repeat 4 repeat 3 go right go left end right end end

4-9. Mainクラス

メイン処理を行うクラスです。

import java.io.BufferedReader;
import java.io.FileReader;

public class Main {
    public static void main(String[] args) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader("program.txt"));
            String text;
            while ((text = reader.readLine()) != null) {
                System.out.println("text = \"" + text + "\"");
                Node node = new ProgramNode();
                node.parse(new Context(text));
                System.out.println("node = " + node);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4-10. 実行結果

text = "program end"
node = [program []]
text = "program go end"
node = [program [go]]
text = "program go right go right go right go right end"
node = [program [go, right, go, right, go, right, go, right]]
text = "program repeat 4 go right end end"
node = [program [[repeat 4 [go, right]]]]
text = "program repeat 4 repeat 3 go right go left end right end end"
node = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]]

5. メリット

Interpreterパターンを利用することで、規則の追加や変更が容易になります。 Interpreterパターンの特徴の1つに、「1つの規則を1つのクラスで表す」というものが挙げられます。つまり、新しい規則を追加する場合はNodeクラスのサブクラスを追加するだけで良くなります。 また、規則を修正する場合も、Nodeクラスのサブクラスを修正するだけになります。

6. GitHub

7. デザインパターン一覧

8. 参考

今回の記事、及びサンプルプログラムは、以下の書籍を元に作成させて頂きました。

大変分かりやすく、勉強になりました。感謝申し上げます。 デザインパターンやサンプルプログラムについての説明が詳細に書かれていますので、是非書籍の方もご覧ください。