디자인 패턴 톺아보기 - Interpreter Pattern
Updated:
1. 인터프리터 패턴(Interpreter Pattern) 이란?
GOF 에서 말하는 인터프리터 패턴의 목적은 아래와 같습니다.
Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
어떤 언어에 대해, 그 언어의 문법에 대한 표현을 정의하면서 그것(표현)을 사용하여 해당 언어로 기술된 문장을 해석하는 해석자를 함께 정의합니다.
1.1. 구조
Sample / Sequence Diagram
- AbstractExpression
- 추상 구문 트리에 속한 모든 노드에 해당하는 클래스들이 공통으로 가져야 할 interpret() 연산을 추상 연산으로 정의합니다.
- TerminalExpression
- 문법에 정의한 터미널 기호와 관련된 해석 방법을 구현합니다. 문장을 구성하는 모든 터미널 기호에 대해서 해당 클래스를 만들어야 합니다.
- NonterminalExpression
- 문법의 오른편에 나타나는 모든 기호에 대해서 클래스를 정의해야 합니다.
- Context
- 번역기에 대한 포괄적인 정보를 포함합니다.
- Client
- 언어로 정의한 특정 문장을 나타내는 추상 구문 트리입니다. 이 추상 구문 트리는 NonterminalExpression과 TerminalExpression 클래스의 인스턴스로 구성됩니다. 이 인스턴스의 Interpret() 연산을 호출합니다.
1.2. 사용 방법
- 사용자는 NonterminalExpression과 TerminalExpression 인스턴스들로 해당 문장에 대한 추상 구문 트리를 만듭니다. 그리고 사용자는 Interpret() 연산을 호출하는데, 이때 해석이 필요한 문맥 정보를 초기화합니다.
- 각 NonterminalExpression 노드는 또 다른 서브 표현식에 대한 Interpret()를 이용하여 자신의 Interpret() 연산을 정의합니다. 이 연산은 재귀적으로 Interpret() 연산을 이용하여 기본적 처리를 담당합니다.
- 각 노드에 정의한 Interpret() 연산은 해석자의 상태를 저장하거나 그것을 알기 위해서 문맥 정보를 이용합니다.
1.3. 장/단점
- Advantages (+)
- Makes changing the grammar easy.
- Makes adding new kinds of interpret operations easier.
- Disadvantages (–)
- Makes representing complex grammars hard.
1.4. 고려사항
- Consider the left design (problem):
- Hard-wired expression.
- Consider the right design (solution):
- Separated expression.
2. 인터프리터 패턴(Interpreter Pattern) 사용예시
인터프리터 패턴은 다음 경우에 사용합니다.
- 해석이 필요한 언어가 존재하거나 추상 구문 트리로서 그 언어의 문장을 표현하고자 한다면 해석자 패턴을 사용할 때이다.
2.1. GOF 패턴
2.1.1. AbstractExpression
abstract class AbstractExpression {
private String name;
public AbstractExpression(String name) {
this.name = name;
}
public abstract void interpret(Context context);
public String getName() {
return name;
}
// Defining default implementation for child management operations.
public boolean add(AbstractExpression e) {
return false;
}
}
2.1.2 TerminalExpression
class TerminalExpression extends AbstractExpression {
public TerminalExpression(String name) {
super(name);
}
public void interpret(Context context) {
// ...
}
}
2.1.3. NonterminalExpression
class NonTerminalExpression extends AbstractExpression {
private List<AbstractExpression> expressions = new ArrayList<AbstractExpression>();
public NonTerminalExpression(String name) {
super(name);
}
public void interpret(Context context) {
System.out.println(getName() + ": ");
for (AbstractExpression expression : expressions) {
System.out.println(" interpreting ... " + expression.getName());
expression.interpret(context);
}
System.out.println(getName() + " finished.");
}
// Overriding the default implementation.
@Override
public boolean add(AbstractExpression e) {
return expressions.add(e);
}
}
2.1.4. Context
class Context {
// Input data and workspace for interpreting.
}
2.1.5. Client
public class Main{
// Running the Client class as application.
public static void main(String[] args) {
// Building an abstract syntax tree (AST).
AbstractExpression ntExpr2 = new NonTerminalExpression("ntExpr2");
ntExpr2.add(new TerminalExpression(" tExpr3"));
ntExpr2.add(new TerminalExpression(" tExpr4"));
ntExpr2.add(new TerminalExpression(" tExpr5"));
AbstractExpression ntExpr1 = new NonTerminalExpression("ntExpr1");
ntExpr1.add(new TerminalExpression(" tExpr1"));
ntExpr1.add(ntExpr2);
ntExpr1.add(new TerminalExpression(" tExpr2"));
Context context = new Context();
// Interpreting the AST (walking the tree).
ntExpr1.interpret(context);
}
}
결과는 아래와 같습니다.
ntExpr1:
interpreting ... tExpr1
interpreting ... ntExpr2
ntExpr2:
interpreting ... tExpr3
interpreting ... tExpr4
interpreting ... tExpr5
ntExpr2 finished.
interpreting ... tExpr2
ntExpr1 finished.
참고 자료
Leave a comment