디자인 패턴 톺아보기 - Interpreter Pattern

Updated:

1. 인터프리터 패턴(Interpreter Pattern) 이란?

GOF 에서 말하는 인터프리터 패턴의 목적은 아래와 같습니다.

Given a language, define a representation for its grammar along with an inter­preter that uses the representation to interpret sentences in the language.

어떤 언어에 대해, 그 언어의 문법에 대한 표현을 정의하면서 그것(표현)을 사용하여 해당 언어로 기술된 문장을 해석하는 해석자를 함께 정의합니다.

1.1. 구조

Sample / Sequence Diagram

image

  • 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