디자인 패턴(구)/행위 패턴

해석자(Interpreter) 패턴이란?

공대키메라 2022. 4. 18. 21:48

1. 해석자(Interpreter) 패턴이란?

의도

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

 

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

 

이를 이용하면 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있다.

사용 시기

  • 해석이 필요한 언어가 존재하거나 추상 구문 트리로서 그 언어의 문장을 표현하고자 할 때.

GoF의 설명에 따르면, 해석자 패턴이 잘 먹힐때는 정의할 언어의 문법이 간단할 때 이다. 

구조

  • AbstrtactExpression : 추상 구문 트리에 속한 모든 노드에 해당하는 클래스들이 공통으로 가져야 할 Interpret() 연산을 추상 연산으로 정의
  • TerminalExpression : 문법에 정의한 터미널 기호와 관련된 해석 방법을 구현.
  • NonterminalExpression : 문법의 오른편에 나타나는 모든 기호에 대해서 클래스를 정의해야 함. 
  • Context : 번역기에 대한 포괄적인 정보를 포함
  • Client : 언어로 정의한 특정 문장을 나타내는 추상 구문 트리

2. 구현

다시 한번 의도를 상기시키고 가자.

 

인터프레터 패턴은 뭐다?

 

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

 

우리는 사칙 연산을 Stack으로 구현할 것이다. 이것을 해석자 패턴으로 쓸 것이다. 

App.java

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

PostfixExpression.java

import java.util.Map;
public interface PostfixExpression {
    int interpret(Map<Character, Integer> context);
}

VariableExpression.java

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

MinusExpression.java

package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;

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.java

package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;

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

PostfixParser.java

package me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after;

import java.util.Stack;

import static me.whiteship.designpatterns._03_behavioral_patterns._15_interpreter._02_after.PostfixExpression.*;

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

다시 한 번 해석자 패턴을 사용하는 시기를 말하자면...

 

해석이 필요한 언어가 존재하거나 추상 구문 트리로서 그 언어의 문장을 표현하고자 할 때이다.

 

구조를 다시 살펴보자.

 

Expression 인터페이스에 해당하는 PostfixExpression.java를 생성했다.

 

내부에 선언된 메소드인 interpret은 Map을 받는다.(Map<Character, Integer> context)

 

여기서 context 는 우리가 해결하고자 하는 언어 혹은 규칙을 지칭하려고 이렇게 이름을 지은 것 같다. 

 

이 Expression 인터페이스를 구현한 TerminalExpression은 VariableExpression.java, MinusExpression.java, PlusExpression.java이 있다.

 

이러한 것을 PostfixParser를 통해서 받아서 해석해주는 것 같다. 

 

그러므로 PostfixParser는 NonTerminalExpression으로 보인다. Non-Terminal이 정확히 어떤 뜻인지 인터넷에서 찾아봤는데, Non Terminal은 규칙을 선택하고 위치에 상관없이 바꿀 수 있는 요소라고 한다. 

 

우리가 입력한 규칙에 따라서 (위의 코드에서는 "xyz+-") 어떻게 결과를 출력해 낼지 해석해내는 부분이 NonTerminalExpression이고, 그것을 담당하는 것은 PostfixParser로 보인다.

 

이번 공부 내용에서는 어떤것이 TerminalExpression, NonTerminalExpression에 해당하는지 솔직히 잘 모르겠다.

 

직접 예제를 구성해서 시늉이라도 맞추고 싶었는데 더 좋은 예제를 찾기가 힘들었다.

 

Youtube에서 찾아보면 있긴 한데 무언가 이것이 훨씬 깔끔하다.

 

장점

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

단점

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

이번 글은 약간 많이 빈약하다. 

 

코드도 사실 강의를 보고 내가 직접 변경한것이 아니라 찜찜하다.

 

무언가 잘못됐거나 이상한 부분이 있으면 적극적 피드백을 부탁 헤엉