디자인 패턴

인터프리터 패턴 (Interpreter Pattern)

개발정리 2022. 8. 3. 22:31

인터프리터 패턴


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

  • 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있다. ex. 정규표현식

 

[참고]

인터프리터는 사람이 작성한 코드를 하드웨어가 이해할 수 있도록 변환해주는 장치이다.

 

 

다이어그램을 보면 컴포짓 패턴과 매우 유사하다.

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

 

 

 

인터프리터 패턴 적용


후위연산을 하는 코드가 있다고 하자. 

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

  private void calcultate() {
    Stack<Integer> st = new Stack<>();

    for(char c : this.expression.toCharArray()) {
      switch (c) {
        case '+':
          st.push(st.pop() + st.pop());
          break;
        case '-':
          int right = st.pop();
          int left = st.pop();
          st.push(left - right);
          break;
        default:
          st.push(Integer.parseInt(c+""));
      }
    }
    System.out.println(st.pop());
  }
}

 

"123+-" 같은 연산을 문법으로 정의하여 재사용 할 것이라면 "123+-" 를 트리구조로 표현할 수 있다. 

"123" 은 그대로 "123" 반환하면 되는 TerminalExpression 이다. 

 

하지만 "+-" 와 같은 Expression 은 다른 두개의 Expression 을 Interpreter 한 다음 그 결과를 연산하는 Non TerminalExpression 이다.

 

 

App 

public class App {
  public static void main(String[] args) {
    PostfixExpression expression = PostfixParse.parse("xyz+-a+");
    int result = expression.interpreter(Map.of('x',1,'y',2,'z',3, 'a',4));
    System.out.println(result);
  }
}

PostfixParse.parse 메소드로 문자열을 파싱한다.

 

 

PostfixParser

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

}

parse 메소드를 보면 받은 문자열을 Character 타입으로 나눈 뒤 getExpression 메소드를 사용하여 stack 에 넣어준다.

그런데 parse 의 반환 타입과 스택의 타입을 보면 모두 PostfixExpression 인 것을 확인 할 수 있다.

 

 

PostfixParse

public interface PostfixExpression {
  int interpreter(Map<Character, Integer> context);
}

 

모든 Expression 들은 위 인터페이스를 구현하면 된다.

 

 

먼저 값을 그대로 반환하는 Terminal Expression 인 VariableExpression 을보자.

public class VariableExpression implements PostfixExpression {

  private Character character;

  public VariableExpression(Character character) {
    this.character = character;
  }

  @Override
  public int interpreter(Map<Character, Integer> context) {
    return 0;
  }
}

 

 

Non Terminal Expression 인 PlusExpression 과 MinusExpression 은 아래와 같다.

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 interpreter(Map<Character, Integer> context) {
    return left.interpreter(context) + right.interpreter(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 interpreter(Map<Character, Integer> context) {
    return left.interpreter(context) - right.interpreter(context);
  }
}

 

 

이를 디버거를 통해 보면 아래와 같고, 이는 트리 형태를 가지고 있다.

 

 

 

장점과 단점


장점

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

 

단점

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

 

 

 

실무에선 어떻게 쓰이나?


자바

  • 정규표현식

 

스프링

  • expression language

 

 

 

참고


 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com