본문 바로가기
Study/디자인 패턴

[C#/Unity][디자인패턴] 인터프리터 패턴(Interpreter Pattern)

by 스테디코디스트 2023. 12. 12.
반응형

1. 인터프리터 패턴이란?

- 자주 등장하는 문법을 별개의 간단한 언어로 정의하고 재사용하는 패턴.

- Expression이라는 추상 클래스를 만들어 사용하는 경우가 많음.

- 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있음.

- 트리구조로 인해 컴포지트 패턴과 유사한 형태를 띈다.

 

2. 인터프리터 패턴을 사용하는 경우

- 특정 언어 또는 문법이 반복되서 사용되는 경우

 

3. 인터프리터 패턴의 장점

1) 캡슐화 : 문법과 해석을 기본 로직에서 분리하여 별도의 클래스로 캡슐화되므로 모듈화되어 유지보수가 쉬워진다.

2) 쉬운 확장 : Expression 클래스에서 파생된 새로운 구현 클래스만 추가하면 DSL을 쉽게 확장할 수 있다.

3) 가독성 : 문법과 규칙을 계층구조를 이용해 명시적으로 모델링하기 때문에 코드의 가독성이 높아진다.

4) 재사용 가능성 : DSL의 여러 인스턴스에 대해 동일한 표현식 클래스와 인터프리터 로직을 사용할 수 있으므로 재사용성을 촉진한다.

cf) DSL(Domain-Specific Languages) : 도메인 특화 언어

- 특정 분야에 최적화된 프로그래밍 언어

 

4. 인터프리터 패턴의 단점

1) 복잡성 : DSL이 크고 복잡한 경우 많은 별도의 클래스가 생기므로 관리 및 이해가 어려워질 수 있다.

2) 성능 : 종종 재귀와 객체 생성을 포함하므로 규모가 크거나 복잡한 경우 오버헤드가 발생할 수 있다.

3) 제한된 적용 가능성 : 문법이 너무 자주 진화하는 경우엔 유지 및 관리에 비용이 더 들 수 있다.

4) 불완전한 솔루션 : 인터프리터 패턴만으로는 렉싱, 에러 핸들링, 최적화와 같은 케이스를 처리하기 어려울 수 있다.

 

5. 코드 구현

1) 구조 - 트리구조

- Context : Expression에서 사용하는 모든 데이터들이 저장되어있는 공간

- Expression : 일련의 규칙을 계산하여 결과값을 반환

- Terminal Expression : Expression을 포함하지 않고, 계산된 결과를 반환

(종료를 포함함 - 더이상 다른 문자로 치환될 수 없는 종점 문자임을 의미)

- Non Terminal Expression : Expression을 참조하여, 종료를 하지않고 다음 규칙으로 넘기는 역할을 함(다른 문자로 치환됨)

 

2) 소스코드(C#)

- 패턴 적용 전

using System;
using System.Collections.Generic;

namespace StudyCSharp
{
    class Program
    {
        // 기본 코드
        private string expression;

        static void Main(string[] args)
        {
            // 이런식의 계산을 앞으로 많이 사용하게 된다? -> 인터프리터 패턴을 이용해 정의해서 사용!
            Program p = new Program { expression = "123+-" };
            p.calculate();
        }

        private void calculate()
        {
            // 후위 표기법을 이용한 계산 로직
            Stack<int> stack = new Stack<int>();

            foreach (char c in this.expression)
            {
                switch(c)
                {
                    case '+':
                        stack.Push(stack.Pop() + stack.Pop());
                        break;
                    case '-':
                        int right = stack.Pop();
                        int left = stack.Pop();
                        stack.Push(left - right);
                        break;
                    default: 
                        stack.Push(c - '0');
                        break;
                }
            }

            Console.WriteLine(stack.Pop());
        }
    }
}

 

- 패턴 적용 후

using System;
using System.Collections.Generic;

namespace StudyCSharp
{
    // Context
    // 사용할 데이터들을 담음
    class Context
    {
        public Dictionary<char, int> dictionary = new Dictionary<char, int>();
    }

    // Expression
    // 인터프리터 기능을 추상화한 추상 클래스
    abstract class PostfixExpression
    {
        public abstract int Interpret(Context context);
    }

    // Terminal Expression
    // 인터프리터가 끝나는 부분 -> 또 다른 표현식 아닌 종점(문자)을 반환
    class VariableExpression : PostfixExpression
    {
        public char variable;

        public override int Interpret(Context context)
        {
            return context.dictionary[variable];
        }
    }

    // Non Terminal Expression -> 다음 표현식인 Expression을 반환
    class PlusExpression : PostfixExpression
    {
        public PostfixExpression left;
        public PostfixExpression right;

        public override int Interpret(Context context)
        {
            return left.Interpret(context) + right.Interpret(context);
        }
    }

    // Non Terminal Expression
    class MinusExpression : PostfixExpression
    {
        public PostfixExpression left;
        public PostfixExpression right;

        public override int Interpret(Context context)
        {
            return left.Interpret(context) - right.Interpret(context);
        }
    }

    // 별도의 Parse 클래스
    // 특정 규칙을 가진 입력을 지정된 기능을 수행하는 문법으로 변환
    class PostfixParser
    {
        public static PostfixExpression parse(string expression)
        {
            Stack<PostfixExpression> stack = new Stack<PostfixExpression>();

            foreach(char c in expression)
            {
                stack.Push(getExpression(c, stack));
            }

            return stack.Pop();
        }

        private static PostfixExpression getExpression(char c, Stack<PostfixExpression> stack)
        {
            switch (c)
            {
                case '+':
                    return new PlusExpression() { left = stack.Pop(), right = stack.Pop() };
                case '-':
                    PostfixExpression right = stack.Pop();
                    PostfixExpression left = stack.Pop();
                    return new MinusExpression() { left = left, right = right };
                default:
                    return new VariableExpression() { variable = c };
            }
        }
    }

    // Client
    // 인터프리터로 정의한 특정 문법을 이용해 결과 도출
    class Program
    {
        static void Main(string[] args)
        {
            // 표현식 입력
            PostfixExpression expression = PostfixParser.parse("xyz+-"); 
            // 결과 : x - (y + z)

            // 표현식 해석
            int result = expression.Interpret(new Context { dictionary = { { 'x', 1 }, { 'y', 2 }, { 'z', 3 } } });
            // 결과 : 1 - (2 + 3) = -4

            // 출력 : -4
            Console.WriteLine(result);
        }
    }
}

<참고 사이트>

 

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

인터프리터 패턴 (Interpreter Pattern) 이란? 인터프리터의 의미 자체는 보통 해석해주거나 통역해주는 역할을 가진 사람 혹은 물건이다. 악보를 음악으로 변환하는 것도 이러한 역할의 하나이기 때

jake-seo-dev.tistory.com

 

 

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

(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다. 코드는 GitHub 에 있습니다 #1. 객체 생성 관련 패턴 #2. 구조 관련 패턴 #3. 행동 관련 패턴 싱글톤 패턴 팩토

thalals.tistory.com