Java/Design Pattern

[디자인 패턴] 행동 패턴 - 인터프리터 패턴 (Interpreter Pattern)

민돌v 2023. 3. 31. 18:48
728x90
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 

 


✔️ 인터프리터 패턴이란 (Interpreter Patterns)

자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴
  •  컴퓨터 사이언스에서 인터프리터란, 원시 코드 (소스코드)를 코드를 읽는 주체 즉, 기계언어(목적 코드) 로 변환하는 프로그램을 의미합니다.
  • 현실에서(?)의 인터프리터는, 통역하는 사람 or 연주자 (악보를 음악으로 변환) 로도 사용된다고 합니다.
  • 👏🏻 즉, 인터프리터 패턴의 목적은 자주 사용되는 특정한 문법적 규칙이 존재한다면, 이를 일련의 규칙을 통해 언어/문법으로 규격화하여 해석하는 목적을 가지는 패턴입니다. (ex 정규표현식)

안터프리터 패턴의 구조

 

인터프리터 패턴 구조를 짧게 살펴보겠습니다.

  • context : Expression 에서 사용하는 모든 데이터들이 저장되어있는 공간입니다.
  • Expression : 일련의 규칙을 계산하여 결과값을 반환합니다.
  • TerminalExpression : Expression을 포함하지 않고, 계산된 결과를 반환합니다. (종료를 포함함 - 더이상 다른 문자로 치환될 수 없는 종점 문자임을 의미합니다.)
  • NonTerminalExpression : Expression 을 참조하여, 종료를 하지않고 다음 규칙으로 값을 넘기는 역할을 합니다. (다른 문자로 치환됨)

 

→ 인터프리터 패턴은 이 NonTerminalExpression 을 통해 해석한 문자를 다음 해석이 가능한 또다른 Expression으로 넘기면서, TerminalExpression만 남으면, 해석된 마지막 문자를 반환하여 interpreter 로써의 기능을 수행하는 구조를 가집니다.

→ 즉, 트리구조를 가집니다.

사진출처 : (좌) https://gngsn.tistory.com/148 (우) https://sbcode.net/python/interpreter/

 

기본적인 구조는 이러하니, 이제 컴공시간에만 사용되는 후위 연산자를 인터프리터 패턴을 이용하여 구현해 보겠습니다.


✔️ 인터프리터 패턴 사용 예시

후위 표기법은, 중위 연산자와 다르게 (1 - 3 = -2) 연산자가 나오면 앞의 두 숫자를 연산하는 형식입니다. (1 3 - = -2)
  • 다음과 같은 후위 표기법을 연산하는 코드가 존재한다고 할 때, 만약 "123+-" 이란 형식에 대한 연산을 자주 반복해서 사용할 것이라면
  • 인터프리터 패턴을 사용하여 이를 "xyz+-" 라는 하나의 형식으로 지정하여 문법으로써 지정할 수 있습니다.

 

//후위 표현식
@AllArgsConstructor
public class PostfixNotation {

    private final String 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());
    }
}

 

  • xyz는 그대로 x면 x, y면 y, z면 z를 반환하면 되는 TerminalExpression가 되어 종료를 포함합니다.
  • 하지만 "+-"과 같은 Expression은 다른 두개의 Expression을 interpret한 다음 그 결과를 연산하는 Non Termianl Expression 입니다.

✔️ 이를 인터프리터 패턴을 사용하여 구격화된 문법으로 사용하는 코드로 구성해보겠습니다.

 

📗 PostfixExpression.Class

먼저 인터프리터 기능을 추상화한 Expression 클래스 입니다. 이 인터페이스의 구현체를 각가 TerminalExpression 과 NonTerminalExpression 의 성질을 담은 구현체로 기능을 캡슐화하여 사용합니다.

public interface PostfixExpression {

    int interpret(Map<Character, Integer> context);

}

 

📗 VariableExoression.Class

  • TerminalExpression 구조를 가지는 VarialbeExpression 클래스입니다.
  • TerminalExpression 이기 때문에, 또다른 문자(표현식) 인 Expression을 반환하지 않고 종점(문자)를 반환합니다.
//Terminal Expression
@AllArgsConstructor
public class VariableExpression implements PostfixExpression {

    private Character varialbe;

    @Override
    public int interpret(Map<Character, Integer> context) {
        return context.get(varialbe);
    }
}

 

📗 PlusExpression.Class + MinusExpression.Class

  • 다음으로는 Non TerminalExpresiion 구조를 가지는 연산자 클래스입니다.
  • 두 후위 표현식의 연산자를 구현하는 클래스는, Non TerminalExpression 이기 때문에 추가로 다음 표현식인 Expression을 반환합니다.
@AllArgsConstructor
public class PlusExpression implements PostfixExpression {

    private PostfixExpression left;
    private PostfixExpression right;

    @Override
    public int interpret(Map<Character, Integer> context) {

        return left.interpret(context) + right.interpret(context);
    }
}

@AllArgsConstructor
public class MinusExpression implements PostfixExpression {

    private PostfixExpression left;
    private PostfixExpression right;

    @Override
    public int interpret(Map<Character, Integer> context) {

        return left.interpret(context) - right.interpret(context);
    }
}

 

📗 Client 코드와 parser class

  • client : 클라이언트는 특정하게 반복되는 형태를 문법으로 만든 expression을 통해 값을 주입하여 결과를 얻습니다.
  • parse : Expression을 반환하는 별도의 parser  클래스가 존재하는 이유는, 클라이언트가 입력한 특정 규칙을 지정된 기능(후위 연산)을 수행하는 문법으로 변환하기 위함입니다,
//Client
public class App {

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

//Parser
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 의 구조를 살펴보면 아래와 같은 Non-Terminal Expression 과 Terminal Expression으로 이루어진 트리 구조로 구성되어집니다. 

 

 

 


✔️ Java 와 Spring 에서의 인터프리터

1. 자바의 정규표현식

https://kingchan223.tistory.com/311

 

2. 스프링의 expression language

https://kingchan223.tistory.com/311


이렇게 인터프리터 패턴에 대해서도 공부를 해보았지만,, 솔직히 이 패턴을 실무에서 사용하는 일이 있을까.. 싶긴합니다.

팀장님의 경우에는 굳이 사용한다면 복작한 request 데이터를 받을 때 받는 쪽과의 규칙을 정해서 사용할 수 도 있지 않을까~~ 하셨지만

저는! 또 다른 적용 가능한 사례가 생각나지는 않네요! 

인터프리터를 패턴을 적용할 기가막힌 상황을 알고계시면 알려주십쇼! 

 

 

끝!

 

 

 

 

 

반응형