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

koline·2023년 9월 7일
0

디자인패턴

목록 보기
16/24

인터프리터 패턴


언어의 다양한 해석 구체적으로 구문을 나누고 그 분리된 구문의 해석을 맡는 클래스를 각각 작성하여 여러형태의 언어 구문을 해석할 수 있게 만드는 패턴이다.



구조


  1. AbstractExpression(추상적인 표현): 구문 트리의 노드에 공통의 인터페이스(API)를 결정하는 역할
  2. terminalExpression(종착점 표현): BNF의 Terminal Expression에 대응하는 역할
  3. NonterminalExpression(비종착점 표현): BNF의 Nonterminal Expression에 대응하는 역할
  4. Context(문맥, 전후 관계): 인터피리터가 구문해석을 실행하기 위한 정보를 제공하는 역할

트리구조로 인해 복합체 패턴과 유사하게 보인다. 여기서 트리는 평가할 표현식을 포함하고, 보통 파서(Parser - Context)에 의해 생성되는데, 파서 자체는 패턴의 일부가 아니다.



구현


인터프리터 패턴 적용 전

// PostfixNotation.java
public class PostfixNotation {
    private final String expression;

    public PostfixNotation(String expression) {
        this.expression = expression;
    }

    public void calculate() {
        Stack<Integer> numbers = new Stack<>();

        for (char c : 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());
    }
}

// Client.java
public class Client {
    public static void main(String[] args) {
        PostfixNotation notation = new PostfixNotation("123+-");
        notation.calculate();
    }
}

// 실행 결과
-4


인터프리터 패턴 적용 후

// PostfixParser.java (Context)
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);
        }
    }
}

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

// PlusExpression.java (NonTerminalExpression)
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);
    }
}

// MinusExpression.java (NonTerminalExpression)
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);
    }
}

// VariableExpression.java (TerminalExpression)
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);
    }
}

// Client.java (Client)
public class Client {
    public static void main(String[] args) {
        PostfixExpression expression = PostfixParser.parse("xyz+-");
        int result = expression.interpret(new HashMap<Character, Integer>() {
            {
                put('x', 1);
                put('y', 2);
                put('z', 3);
            }
        });
        System.out.println(result);
    }
}

// 실행 결과
-4

실행 결과는 동일하게 나온다. 이 패턴을 적용하면 +식과 -식이 나올 때 처리 방식을 각각 분리해서 AbstractExpression을 상속받는 클래스로 각각 구현하여 문법으로 만든다. 그리고 처리의 종착점이 되는 숫자(변수)도 AbstractExpression을 상속받는 클래스로 구현한다.

parse() 메소드를 통해 후위표현식이 중위표현식으로 파싱되고, 이후에 interpret()메소드를 통해 x, y, z에 각각 HashMap으로 값을 보내주어 해독하여 결과값을 리턴한다.

이렇게 구현할 경우 이후에 곱하기나 나누기 연산이 추가되더라도 확장이 용이하고, 재사용성이 커진다.



장점

  1. 문법과 해석을 기본 로직에서 분리하여 별도의 클래스로 캡슐화 되므로 모듈화되어 유지보수하기 쉬워진다.
  2. Expression 클래스에서 파생된 새로운 구현 클래스만 추가하면 DSL 을 쉽게 확장할 수 있다.
  3. 문법과 규칙을 계층구조로 명시적으로 모델링하기 때문에 코드의 가독성이 높아진다.
    문법과 요소의 관계를 이해하는데 도움이 된다.
  4. DSL 의 여러 인스턴스에 대해 동일한 표현식 클래스와 인터프리터 로직을 사용할 수 있으므로 재사용성을 촉진한다.

단점

  1. DSL 이 크고 복잡한 경우 많은 별도의 클래스가 생기므로 관리 및 이해가 어려워질 수도 있다.
  2. 종종 재귀와 객체 생성을 포함하므로 규모가 크거나 복잡한 표현식의 경우 오버헤드가 발생한다.
  3. 문법이 너무 자주 진화하는 경우엔 유지 관리하는데에 비용이 더 들 수도 있다.
    주로 간단하고 잘 정의된 문법에 효과적이다.
  4. 인터프리터 패턴만으로는 렉싱, 에러 핸들링, 최적화와 같은 케이스의 측면을 처리하기 어려울 수 있다.



참고


[디자인패턴] 디자인패턴이란? - 생성패턴, 구조패턴, 행위패턴

인터프리터 패턴 (Interpreter Pattern) 이란?

profile
개발공부를해보자

0개의 댓글