CS/디자인패턴

[디자인패턴] Interpreter 패턴( 통역 패턴)

mabb 2023. 6. 25. 18:00
반응형

▶디자인 패턴 ( Design Pattern)

:프로그램 개발 시 문제 해결을 위하여 빈번히 사용되는 개발자들의 경험, 내적인 축적에 대하여,
GoF(Gang of Four) 라 불리는 4인의 개발자들이 각각을 패턴으로 정의하고 이름을 붙였다.
이를 디자인 패턴 (Design Pattern) 이라고 한다.
 23개의 디자인 패턴을
『Elements of Reusable Object-Oriented Software』
라는 책으로 발간하였다.

Gang of Four 분들은 모두 안경을 썼다.

 

▷디자인 패턴의 용어를 빌리면 서로의 아이디어를 보다 용이하게 비교, 논의할 수 있게 된다.
▷재사용과 기능확장이 쉬운 소프트웨어를 만들기 위한 유익한 기법이 바로 디자인 패턴이다.

 

---------------------------------------------------------
1. Iterator
2. Adapter
3. Template Method
4. Factory Method
5. Singleton
6. Prototype
7. Builder
8. Abstract Factory
9. Bridge
10. Strategy
11. Composite
12. Decorator
13. Visitor
14. Chain of Responsibility
15. Facade
16. Mediator
17. Observer
18. Memento
19. State
20. Flyweight
21. Proxy
22. Command
23. Interpreter
---------------------------------------------------------

▶Interpreter 패턴

"문법을 객체지향 클래스로 표현"
"미니언어"
"정규표현식"
"BNF표기법 -> 언어"


-왜 사용하는가
-클래스다이어그램
-각 클래스(인터페이스)의 역할
-Java소스

▶Interpreter 패턴에 대한 이해

-Interpreter는 통역이라는 의미이다.

-언어 내에서 '미니 언어'를 사용하는 패턴이 Interpreter패턴이다.

-언어 문법을 객체 지향으로 표현하는 것이 Interpreter패턴이다.

-미니언어의 대표적인 예로는 정규표현식(RegEX)가 있다.

-BNF(Backus-Naur Form,베커스 나우르 폼)으로 언어의 문법을 수식으로 표현할 수 있다.

-미니언어라는 간접적인 처리방법을 이용하면 문제가 생겼을 때 미니 언어 단에서만 수정을 할 수 있다.

-문법 규칙은  자신이 등장하는 재귀적 정의가 많다.

-재귀적 정의가 없는 문법 표현을 terminal expression이라고 칭한다.

-재귀적 정의가 있는 문법 표현을 interminal expression이라고 칭한다.

-terminal expression 이 문법 규칙의 종착점이다.

-구문해석은 재귀적으로 parse를 진행한다. 재귀적인 parse의 종착점이 terminal expression이다.

-구문해석(Parse)은 문자열을 분해 및 해석하여 메모리 상에 구문 트리를 만드는 처리를 말한다. 토큰을 구문해석하여 구
문트리를 만든다.

-어구해석(Lex)은 문자를 분해하여 토큰으로 만드는 처리를 말한다. 문자를 어구해석하여 토큰을 만든다.

-토큰(token)은 구문해석의 처리단위가 되는 분해된 문자조각이다.

-java.util.StringTokenizer클래스로 문자열을 토큰으로 분해하여 다룰 수 있다.

-구문은 재귀적으로 분석하게 되는데 Terminal Expression과 Nonterminal Expression을 Composit패턴으로 재귀적으로 parse 한다.

 

▶왜 사용하는가

문법을 객체지향적으로 표현하기 위해 사용한다. 정규표현식이 대표적인 인터프리터패턴이다.

▶클래스다이어그램

Interpreter패턴의 클래스다이어그램

 

▶각 클래스(인터페이스)의 역할

▷AbstractExpression 역할 - Node.java

parse() 기능을 선언한다. Terminal Expression과 Nonterminal Expression을 동일시하게 한다.

▷Terminal Expression 역할 -PrimitiveCommandNode.java

재귀적 구문분석에서의 종착지가 되는 역할이다.

▷NonTerminalExpression 역할 -ProgramNode.java, CommandLineNode.java, CommandNode.java, RepeatCommandNode.java

재귀적으로 하위 노드에 Context를 넘겨주면서 parse 하는 역할이다.

▷Context역할 - Context.java

인터프리터가 구문해석을 실행하기 위한 정보 및 기능을 제공한다. 구문분석할 Text를 StringTokenizer로 토큰 분해하고, 현재토큰, 다음토큰, 토큰 숫자 변환등의 기능을 제공한다. 인터프리터는 이 Context를 가지고 구문분석(parse)을 수행한다.

▷Client 역할 - Main.java

구문분석을 수행시키는 역할을 한다.

 

▶Java 소스

*java1.8 이전 버전을 사용하는 교재를 참고하였습니다.
Java API 문서 등을 참조하여 제네릭을 적용하는 등 소스를 개선할 필요가 있습니다.
(https://docs.oracle.com/en/java/javase/20/docs/api/)

▷AbstractExpression 역할 - Node.java

package main.java.designpattern.interpreter;

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

 

▷Terminal Expression 역할 

PrimitiveCommandNode.java

package main.java.designpattern.interpreter;

//<primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node{
    private String name;
    @Override
    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;
    }
}

 

▷NonTerminalExpression 역할

ProgramNode.java

package main.java.designpattern.interpreter;

//<program> ::= program <command list>
public class ProgramNode extends Node{
    private Node commandListNode;
    @Override
    public void parse(Context context) throws ParseException {
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }
    public String toString(){
        return "[program "+commandListNode+"]";
    }
}

 

CommandListNode.java

package main.java.designpattern.interpreter;

import java.util.ArrayList;

//<command list> ::= <command>* end
public class CommandListNode extends Node{
    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);
            }
        }
    }
    public String toString(){
        return list.toString();
    }
}

 

CommandNode.java

package main.java.designpattern.interpreter;

//<command> ::= <repeat command> | <primitive command>
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);
        }
    }
    public String toString(){
        return node.toString();
    }
}

 

RepeatCommandNode.java

package main.java.designpattern.interpreter;

//<repeat command> ::= repeat <number> <command list>
public class RepeatCommandNode extends Node{
    private int number;
    private Node commandListNode;
    @Override
    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+" ]";
    }
}

 

▷Client 역할 - Main.java

package main.java.designpattern.interpreter;

import java.io.*;

public class Main {
    public static void main(String[] args){
        try{
            File file = new File("program.txt");
            BufferedWriter bf = new BufferedWriter(new FileWriter(file));
            if(!file.exists()){
                file.createNewFile();
            }
            bf.write("program end\n");
            bf.write("program go end\n");
            bf.write("program go right go right go right go right end\n");
            bf.write("program repeat 4 go right end end end\n");
            bf.write("program repeat 4 repeat 3 go right go left end right end end\n");
            bf.close();

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

 

---------------------------------------------------------
※참고자료
-Java언어로 배우는 디자인 패턴 입문
-Headfirst Design Pattern
-나무위키, 위키백과, 네이버백과
-리팩토링구루(https://refactoring.guru/ko)

반응형