디자인패턴 해석자 (Interpreter)

황제낙엽 2008.06.25 23:17 조회 수 : 35 추천:75

sitelink1  
sitelink2  
sitelink3 http://1 
extra_vars4 ko 
extra_vars5 http://blog.naver.com/smurpettt/150000478635 
extra_vars6 sitelink1 
15.     The Interpreter Pattern

어떤 프로그램들은 해당 프로그램의 기능을 기술하기 위한 언어를 갖는 것 자체에서 이득을 얻는 경우가 있다. 일반적으로 Interpreter 패턴은 개발 언어에 대한 문법을 정의하는 문제와 이러한 문법적인 내용을 이용하여 해당 언어로 작성된 선언문을 번역하는 문제를 다루고 있다.

 

프로그램이  다른 것들을 나타내지만 몇 가지는 유사한 경우가 있을 때 Interpreter 패턴은 이러한 경우들을 묘사하는 간단한 언어를 이용할 수 있는 이점이 있고, 언어를 해석하는 프로그램을 갖는다. 문제중의 하나는 우리가 다루어야 할 것이 언어가 도움을 줄 수 있을 때를 어떻게 인식하는가 이다.

 

구조

 

역할

  • AbstractExpression : 구문 트리의 노드에 공통의 인터페이스(API)를 정하는 역할입니다.
  • TerminalExpression : 문법적으로 터미널 토큰 요소를 포함하는 모든 종류의 식을 번역한다.
  • NoterminalExpression : 문법적으로 터미널 토큰 요소를 포함하지 않는 모든 문장을 번역한다.
  • Context : 인터프리터가 구문해석을 수행하도록 정보를 제공하는 역할

TerminalExpression NoterminalExpression

그 이상 전개되지 않는 표현을 ‘TerminalExpression’ 이라고 부릅니다. 버스나 열차의 종착역을 터이널이라고 하는 것과 비슷합니다. 문법규칙의 종착점입니다. 한편 이와 반대를 ‘NoterminalExpression’ 이라 합니다.

 

의도

간단한 언어의 문법을 정의하고 해석할 수 있게 한다.

 

적용시기

  • 개발자가 사용자 명령어를 파싱하기 위해 명령어 번역기를 필요로 할 경우. 사용자는 다양한종류의 쿼리를 입력하여 그것에 해당하는 다양한 응답 메시지를 얻을 수 있다.  
  • 프로그램이 대수적 문자열을 파싱해야 할 경우. 이번 경우는 분명한 사례이다. 프로그램은 사용자가 특정 연산식을 입력하는 경우의 연산 작업에 따른 작업 내용을 수행하도록 요청받게 된다. 이런 과정은 종종 수학적 연산을 필요로 하는 그래픽 소프트웨어에서 발생하는데, 해당 프로그램이 연산식을 기초로 한 곡선이나 표면 등을 렌더링할 때 사용된다.
  • 프로그램이 다양한 종류의 결과물을 산출해야 할 경우. 이 경우는 명확하게 구분 짓기는 조금 어렵지만. 훨씬 유용하게 사용될 수 있는 사례이다. 어떠한 순서로든지 데이터의 열을 출력하고, 다양한 방식으로 정렬하는 프로그램의 경우를 생각해 보자. 이런 프로그램들을 리포트 생성기라고 부른다. 기본 데이터는 관계형 데이터베이스에 저장되어 있는 경우가 많지만, 일반적으로 사용자 인터페이스는 해당 데이터베이스가 사용하는 SQL문보다 훨씬 간단하다. 실제로 단순 리포트 언어가 이런 리포트 프로그램으로 번역되어 SQL문으로 변환될 수 있다.

결론

l         Interpreter 요소를 프로그램에 도입할 때마다 해당 프로그램 사용자가 적합한 언어로 쉽게 명령어를 입력할 수 있는 방법을 제공해야 한다.

l         하나의 언어와 그것에 따른 문법 내용을 소개하는 일은 잘못 쓰여진 용어나 문법적으로 잘못된 위치에 놓인 요소들에 대해 매우 집중적으로 오류를 확인해야 한다. 그러므로 이런 오류 확인 작업을 위한 템플릿 코드가 제공되지 않을 경우 프로그래밍 작업은 엄청난 시간과 노력을 들여야 한다. 게다가 사용자에게 이런 오류 내역을 효과적으로 알려주는 방법은 설계나 구현에 있어서 매우 어려운 작업이다.

l         사용자 인터페이스나 라디오 버튼, 명령어 버튼 및 리스트 상자에서 언어를 자동으로 생성할 수 있는 방법을 생각해 볼 수 있다. 이런 방식은 해당 인터페이스가 언어의 필요성을 제거하는 것처럼 보이겠지만, 결과값과 연산 작업에서의 동일한 요청 내용은 여전히 유효하다. 개발자가 순차적인 작업의 순서를 구체화하기 위한 방안을 분명히 가지고 있었다고 여길 때 해당 언어가 사용자 인터페이스에서 생성되었어도 언어는 이런 작업을 처리하기 위한 훌륭한 도구가 될 수 있다.

l         Interpreter 패턴은 일단 일반 파싱 및 스택 제거 도구를 구현한 수에 그것에 관련된 문법적인 내용의 범위를 확장하거나 해당 내용을 적합하게 갱신할 수 있는 이점이 있다.

l         문법의 내용이 좀더 복잡해지면서 해당 프로그램을 유지하기 위해 하드 디스크를 생성해야 하는 부담도 안게 된다.

 

예제소스

(* 개발 툴 버젼을 올렸더니... 다이어그램 색이 현광색으로 나오네요. - -;) 

 

예제 소스

import java.util.*;

 

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 ParseException {

        if (!token.equals(currentToken)) {

            throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found.");

        }

        nextToken();

    }

    public int currentNumber() throws ParseException {

        int number = 0;

        try {

            number = Integer.parseInt(currentToken);

        } catch (NumberFormatException e) {

            throw new ParseException("Warning: " + e);

        }

        return number;

    }

}

 

public abstract class Node {

    public abstract void parse(Context context) throws ParseException;

}

 

public class ParseException extends Exception {

    public ParseException(String msg) {

        super(msg);

    }

}

 

import java.util.Vector;

 

public class CommandListNode extends Node {

    private Vector list = new Vector();

    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);

            }

        }

    }

    public String toString() {

        return "" + list;

    }

}

 

public class CommandNode extends Node {

    private Node node;

    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);

        }

    }

    public String toString() {

        return node.toString();

    }

}

 

public class PrimitiveCommandNode extends Node {

    private String name;

    public void parse(Context context) throws ParseException {

        name = context.currentToken();

        context.skipToken(name);

        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {

            throw new ParseException(name + " is undefined");

        }

    }

    public String toString() {

        return name;

    }

}

 

public class ProgramNode extends Node {

    private Node commandListNode;

    public void parse(Context context) throws ParseException {

        context.skipToken("program");

        commandListNode = new CommandListNode();

        commandListNode.parse(context);

    }

    public String toString() {

        return "[program " + commandListNode + "]";

    }

}

 

public class RepeatCommandNode extends Node {

    private int number;

    private Node commandListNode;

    public void parse(Context context) throws ParseException {

        context.skipToken("repeat");

        number = context.currentNumber();

        context.nextToken();

        commandListNode = new CommandListNode();

        commandListNode.parse(context);

    }

    public String toString() {

        return "[repeat " + number + " " + commandListNode + "]";

    }

}

 

import java.util.*;

import java.io.*;

 

public class mainClass {

    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();

        }

    }

}

a program.txt 파일

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

 

관련패턴

l        Composite Pattern: 추상 문법tree composite instance가 됨

l        Flyweight Pattern: symbol공유기능

l        Iterator Pattern: 문장 traverse시 사용될 수 있다.

l        Visitor Pattern : 각각 node에서 수행될 행동을 한 class에서 유지하게 할 수 있다.