# 인터프리터 (Interpreter) 패턴

# 인터프리터 패턴이란 무엇인가요?

주로 CS에서 만나볼 수 있는 인터프리터는 사람이 작성한 자연어를 기계어로 바꾸는 일을 하는 컴파일러가 예시가 될 수 있다. 주로 이 패턴은 우리가 같은 문제를 자주 해결해야 하는 상황에 맞닥뜨리는 경우 효과적이다.

TIP

자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴

--> 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있다

image

  • Context 은 모든 Expression에서 사용하는 공통된 정보가 담겨있다.
  • Expression 은 우리가 표현하는 문법을 나타내는데, Context가 들어 있는 것을 볼 수 있다.
  • TerminalExpression 은 그 자체로 종료되는 Expression이다.
  • Non TerminalExpression 은 다른 Expression들을 재귀적으로 참조하고 있는 Expression이다.

TIP

컴포짓 패턴과 매우 유사하며, Abstract Syntax Tree를 구성하게 된다.

# 어떤 상황에서 인터프리턴 패턴을 적용할까?

여기에 스택을 이용한 후위연산 코드가 있다.

import java.util.Stack;

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+-와 같이 계속해서 반복 사용할 경우가 발생한다면, 인터프리터 패턴을 이용하여 해결하는 것이 바람직한 방안으로 제시될 수 있다.

# 인터프리터 패턴 적용 예시

우선 Expression을 생성한다.

import java.util.Map;

public interface PostfixExpression {
    int interpret(Map<Character, Integer> context);
}
  • 상위 인터페이스인 Expression인 PostfixExpression을 만들었으니까 하위의 TerminalExpresion과 NonTerminalExpression을 만들어보자.
import java.util.Map;

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);
    }
}
  • context에서 자기 자신의 값을 찾아 바로 반환하여 로직을 종료하는 TerminalExpresion 인 VariableExpression 을 구현한다.
import java.util.Map;

public class PlusExpression implements PostfixExpression {
    private PostfixExpression left;
    private PostfixExpression right;

    public PlusExpression(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);
    }
}
import java.util.Map;

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);
    }
}
  • PlusExpression, MinusExpression 은 NonTerminalExpression으로 소속 Expression을 실행하는 역할을 수행한다.
import java.util.Stack;

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);
        }
    }
}
  • 위의 코드는 후위 연산식으로 변환하는 Parser이다.
  • 해당 파싱으로 통해서 AST가 생성된다.
import java.util.Map;

public class App {
    public static void main(String[] args) {
        PostfixExpression expression = PostfixParser.parse("xyz+-a+");
        int result = expression.interpret(Map.of('x', 1, 'y', 2, 'z', 3, 'a', 4));
        System.out.println(result);
    }
}
  • 사용측에서는 다음과 같이 사용을 시도하면 된다.

# 인터프리터 패턴의 장단점은 무엇인가요?

장점

  • 자주 등장하는 문제 패턴을 언어와 문법으로 정의할 수 있다.
  • 기존 코드를 변경하지 않고 새로운 Expression을 추가할 수 있다.

단점

  • 복잡한 문법을 표현하려면 Expression과 Parser가 복잡해진다.

# 인터프리터 패턴의 예시는 무엇인가요?

자바

  • 자바 컴파일러
  • 정규 표현식

스프링

  • SpEL (스프링 Expression Language)

# 참고 자료

  • https://kingchan223.tistory.com/311