본문 바로가기
컴퓨터 과학/디자인패턴

[디자인패턴] 행위 패턴(3) : 인터프리터(Interpreter)

by webcodur 2024. 3. 25.
728x90

목차

     

     

    인터프리터(Interpreter)

    인터프리터 디자인 패턴은 주어진 언어에 대해 그 언어의 문법에 대한 표현을 정의하고, 이를 사용하여 해당 언어의 문장을 해석하는 패턴이다. 이 패턴의 어원은 프로그래밍 언어의 '인터프리터'에서 비롯되었다. 인터프리터란 코드를 직접 실행하는 프로그램이나 환경을 의미하는데, 이 패턴은 이러한 개념을 디자인 패턴에 적용한 것이다. 즉, 어떤 입력을 받아 이를 분석하고 실행하는 구조를 디자인 패턴 형태로 구현한 것이다.

     

    컴퓨터가 입력받은 수식을 트리 형태로 만들고 후위 연산을 적용해 실행시키는 것과 마찬가지로 식을 만들어두고 저장했다가 하나씩 실행하는 상황에 사용하면 적합하다. 

     

    패턴 미적용 예시

    C#을 사용한 간단한 게임 개발 상황에서 인터프리터 패턴을 적용하지 않은 경우를 살펴보자. 이 예시에서는 플레이어가 게임 내에서 다양한 명령을 입력하고, 시스템이 해당 명령을 해석하여 실행하는 상황을 가정한다.

    using System;
    
    class Game {
        public void ExecuteCommand(string command) {
            if (command == "move forward") {
                Console.WriteLine("플레이어가 앞으로 이동했다.");
            } else if (command == "move back") {
                Console.WriteLine("플레이어가 뒤로 이동했다.");
            } else if (command == "jump") {
                Console.WriteLine("플레이어가 점프했다.");
            } // 다른 명령들에 대한 처리
            else {
                Console.WriteLine("알 수 없는 명령입니다.");
            }
        }
    }
    
    class Program {
        static void Main(string[] args) {
            Game game = new Game();
            game.ExecuteCommand("jump");
        }
    }
    

    이 코드에서 문제는 명령이 추가될 때마다 ExecuteCommand 메소드가 점점 길어지고 복잡해진다는 것이다. 새로운 명령을 추가하거나 기존 명령을 변경하는 것이 어려워질 수 있다.

     

    패턴 적용 예시

    인터프리터 패턴을 적용하면, 각 명령을 개별 클래스로 분리하여 해석하고 실행할 수 있다. 이 방법은 코드의 유연성과 확장성을 크게 향상시킨다.

    using System;
    using System.Collections.Generic;
    
    // 표현식 인터페이스
    interface IExpression {
        void Interpret(Context context);
    }
    
    // 상황/환경 클래스
    class Context {
        public string Output { get; set; } = "";
    }
    
    // 구체적인 표현식 클래스들
    class MoveForwardExpression : IExpression {
        public void Interpret(Context context) {
            context.Output += "플레이어가 앞으로 이동했다.\\n";
        }
    }
    
    class MoveBackExpression : IExpression {
        public void Interpret(Context context) {
            context.Output += "플레이어가 뒤로 이동했다.\\n";
        }
    }
    
    class JumpExpression : IExpression {
        public void Interpret(Context context) {
            context.Output += "플레이어가 점프했다.\\n";
        }
    }
    
    // 클라이언트 클래스
    class Game {
        private List<IExpression> expressionList = new List<IExpression>();
    
        public void InterpretCommand(string command) {
            switch (command) {
                case "move forward":
                    expressionList.Add(new MoveForwardExpression());
                    break;
                case "move back":
                    expressionList.Add(new MoveBackExpression());
                    break;
                case "jump":
                    expressionList.Add(new JumpExpression());
                    break;
                // 다른 명령어들을 여기에 추가할 수 있음
            }
        }
    
        public void ExecuteCommands() {
            Context context = new Context();
            foreach (var expression in expressionList) {
                expression.Interpret(context);
            }
            Console.WriteLine(context.Output);
        }
    }
    
    class Program {
        static void Main(string[] args) {
            Game game = new Game();
            
            // 명령어를 게임에 추가
            game.InterpretCommand("move forward");
            game.InterpretCommand("jump");
            game.InterpretCommand("move back");
            
            // 추가된 명령어들을 실행
            game.ExecuteCommands();
        }
    }
    

    실행결과

    플레이어가 앞으로 이동했다.
    플레이어가 점프했다.
    플레이어가 뒤로 이동했다.
    

     

    이 코드에서는 IExpression 인터페이스를 정의하여 각 명령어("move forward", "move back", "jump")에 대한 해석을 담당하는 클래스(MoveForwardExpression, MoveBackExpression, JumpExpression)를 구현한다.

     

    Context 클래스는 해석의 결과를 저장하는 역할을 하며, Game 클래스는 사용자의 명령어를 받아 이를 IExpression 을 구현한 클래스 객체로 변환한 후, 모든 명령어를 순서대로 해석하여 실행한다.

     

    인터프리터 패턴을 적용함으로써 각 명령어의 처리 로직을 분리하여 관리할 수 있게 되어, 새로운 명령어 추가나 기존 명령어의 수정이 용이해진다. 이는 코드의 확장성과 유지 보수성을 크게 향상시키는 장점을 가진다.