인터프리터 패턴
인터프리터 패턴이란 무엇입니까?
프로그램을 작성할 때 때때로 프로그램의 전체 동작을 정의할 수 없습니다. 예를 들어, 브라우저를 구축할 때 웹사이트 디자이너가 원하는 웹 페이지 동작 방식에 대한 모든 것을 예측하는 것은 불가능합니다. 이 경우 JavaScript와 같은 해석된 언어를 통해 브라우저는 브라우저 프로그래머가 구현하지 않은 동작을 추가할 수 있습니다.
인터프리터 패턴을 사용하면 이 목적으로 사용되는 인터프리터를 작성할 수 있습니다. 먼저 언어의 문법을 설명하는 규칙에 대한 공식 문법을 정의합니다. 그리고 각 규칙은 클래스에 의해 구현됩니다. 이러한 클래스는 컨텍스트 개체를 공유하고, 컨텍스트 개체를 통해 입력을 받고, 변수 값을 저장하는 등의 작업을 수행합니다.
언제 사용
- 객체 지향 컴파일러 구현에 자주 사용됩니다.
- 인터프리터 패턴은 복합 패턴을 사용할 때 사용할 수 있습니다. 그러나 복합 패턴으로 정의된 클래스가 언어 구조를 정의하는 경우에만 인터프리터 패턴이라고 할 수 있습니다.
- 때로는 런타임에 시스템 설정을 변경하는 데 사용됩니다.
- 언어가 주어지면 인터프리터를 사용하여 문법적 표현을 정의하고 해당 표현을 사용하여 언어의 문장을 해석합니다.
구조

- 해석기 정보(컨텍스트): 모든 표현에 사용되는 일반적인 정보를 포함합니다.
- 추상 구문 트리 인터페이스(AbstractExpression): 우리가 표현하고 있는 문법을 나타냅니다. 컨텍스트가 포함되어 있습니다.
- 종료기호(종료식) : 끝나는 식
- Nonterminal Expression: 다른 표현식을 재귀적으로 참조하는 표현식.
- 문장(클라이언트)을 나타내는 추상 구문 트리
장점과 단점
장점
- 각각의 문법 규칙이 클래스로 표현되기 때문에 언어 구현이 용이합니다.
- 문법은 클래스로 표현되기 때문에 언어를 쉽게 변경하거나 확장할 수 있습니다.
- 프로그램을 해석하는 기본 기능 외에도 클래스 구조에 메서드를 추가하는 것만으로 명확한 출력이나 더 나은 프로그램 식별과 같은 새로운 기능을 추가할 수 있습니다.
불리
- 문법 규칙이 많을수록 더 복잡해집니다.
- 여러 문법이 생성되면 성능 저하가 발생합니다.
예
접미사 작업을 수행하는 코드를 사용하는 인터프리터 패턴을 살펴보겠습니다. (접미사 연산: “123+-” -> “1 – (2 + 3)”)
public class PostfixNotation {
private final String expression;
public PostfixNotation(String expression) { this.expression = expression; }
public static void main(String() args) {
PostfixNotation postfixNotation = new PostfixNotation("123+-");
postfixNotation.calculate();
}
private void calculate() {
Stack<Integer> numbers = new Stack<>();
for (char c: this.expression.toCharArray()) {
switch (c) {
case '+':
numbers.push(numbers.pop() + numbers.pop());
break;
case '-':
int right = numbers.pop();
int left = numbers.pop();
numbers.push(left - right);
break;
default:
numbers.push(Integer.parseInt(c + ""));
}
}
System.out.println(numbers.pop());
}
}
“123+-“, 즉 “xyz+-“와 같은 문법 정의 연산을 재사용한다고 가정합니다.
엑스, 와이, 지변경되지 않은 자체 값을 반환합니다. 다음 단계 없이 자체 값 반환 터미널 표현오전.
한편, 운영자 ‘+‘, ‘–‘는 다른 두 표현식을 해석한 다음(숫자를 숫자로, 텍스트를 문자로 바꾸는 것을 생각할 수 있음) 결과를 계산합니다. 비말단 표현오전.
이 예제를 사용하여 문자열 접미사 작업을 수행하는 인터프리터 패턴을 구현합니다.
먼저 PostFixExpression 인터페이스를 만듭니다.
public interface PostFixExpression {
int interpret(Map<Character, Integer> context);
}
모든 식은 위의 인터페이스를 구현해야 합니다.
먼저 값을 있는 그대로 반환하는 터미널 표현식인 VariableExpression을 만듭니다.
public class VariableExpression implements PostFixExpression {
private Character character;
public VariableExpression(Character character) {
this.character = character;
}
@Override
public int interpret(Map<Character, Integer> context) {
return context.get(this.character); // 생성자 인자로 받은 character 의 키를 가져온다.
}
}
다음으로 비단말식인 더하기 및 빼기 식을 만듭니다.
public class PlusExpression implements PostFixExpression{
private PostFixExpression left;
private PostFixExpression right;
public PlusExpression(PostFixExpression left, PlusExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Map<Character, Integer> context) {
return left.interpret(context) + right.interpret(context);
}
}
public class MinusExpression implements PostFixExpression{
private PostFixExpression left;
private PostFixExpression right;
public MinusExpression(PostFixExpression left, PostFixExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Map<Character, Integer> context) {
return left.interpret(context) - right.interpret(context);
}
}
이제 표현식을 사용하여 해석하는 파서 클래스를 만들어 보겠습니다. 예제에서 볼 수 있듯이 스택을 통해 구현됩니다.
public class PostFixParser {
public static PostFixExpression parse(String expression) {
Stack<PostFixExpression> stack = new Stack<>();
for (char c: expression.toCharArray()) {
stack.push(getExpression(c, stack));
}
return stack.pop();
}
private static PostFixExpression getExpression(char c, Stack<PostFixExpression> stack) {
switch (c) {
case '+' :
return new PlusExpression(stack.pop(), stack.pop());
case '-' :
// 스택에서 먼저 빠져 나오는 값부터 계산해야 후위 연산의 순서가 맞게 된다.
PostFixExpression right = stack.pop();
PostFixExpression left = stack.pop();
return new MinusExpression(left, right);
default:
return new VariableExpression(c);
}
}
}
시험
사용자의 입력 값에 따라 후위 연산으로 계산하는 App 클래스를 구현했습니다. 1에서 26까지의 숫자를 문자(a에서 z까지)로 해석하고 계산하도록 구현되었습니다.
public class App {
public static void main(String() args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Map<Character, Integer> map = new HashMap<>();
System.out.println("알파벳 소문자와 '+', '-' 만 입력해주세요.");
String input = br.readLine();
for (char c: input.toCharArray()) {
if (c != '+' && c != '-') {
map.put(c, c - 96);
}
}
for (Map.Entry<Character, Integer> entrySet: map.entrySet()) {
System.out.println(entrySet.getKey() + " : " + entrySet.getValue());
}
// String 입력값을 넣는다.
PostFixExpression expression = PostFixParser.parse(input);
int res = expression.interpret(map);
System.out.println(res);
}
}

정리하다
- 일반적으로 사용되는 패턴은 아니지만 2진수, 10진수 등 자주 분석해야 하는 프로그램 같은 경우 유용할 수 있습니다.
- 가장 좋은 예는 Java 소스 코드를 JVM이 이해할 수 있도록 바이트 코드로 변환하는 Java 컴파일러입니다.
- Java 및 Spring의 Expression Language의 정규식도 있습니다.