ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 인터프리터 패턴 (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

     
Designed by Tistory.