Jake Seo
제이크서 개발 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (719)
    • AI 서비스 개발 일기 (3)
    • LLM 개발 일기 (1)
    • ------레거시 (2025.08.23 이전)--.. (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • serverless computing
  • prerendering
  • 참조 해제
  • 외래키 제약조건
  • 객체복사
  • 메이븐 페이즈
  • 플라이웨이트패턴
  • 메이븐 라이프사이클
  • Java
  • bean Validation
  • 싱글톤 패턴
  • 도커공식문서
  • 프로그래머의 뇌
  • 느린 쿼리
  • 팩터리 메서드 패턴
  • 추상 팩터리 패턴
  • MSSQL
  • 자바 디자인패턴
  • item8
  • 이펙티브자바
  • Javadoc 자바독 자바주석 주석 Comment
  • 메이븐 골
  • 디자인패턴
  • pnpm
  • 싱글톤
  • 자바
  • rust
  • 자바스크립트
  • 자바 검증
  • NEXT JS
  • 자바스크립트 면접
  • try-with-resources
  • 슬로우 쿼리
  • 토비의 스프링
  • 빈 검증
  • 러스트
  • next js app
  • 스프링 검증
  • 자료구조
  • item9
  • 이펙티브 자바 item9
  • 싱글턴
  • 작업기억공간
  • 이펙티브 자바
  • 자바스크립트 인터뷰
  • Next.js
  • 서버리스 컴퓨팅
  • 알고리즘
  • Pre-rendering
  • item7

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 개발 블로그

Java/자바 디자인 패턴

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

2023. 4. 30. 02:57

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

  • 인터프리터의 의미 자체는 보통 해석해주거나 통역해주는 역할을 가진 사람 혹은 물건이다.
    • 악보를 음악으로 변환하는 것도 이러한 역할의 하나이기 때문에 연주자라는 의미도 있다.
  • 가장 쉽게 볼 수 있는 예로는 정규표현식이 있다.
  • 자주 등장하는 문제를 별개의 언어로 정의하고 재사용하는 패턴이다.
    • 문법에 등장하는 규칙을 클래스로 표현하고 언어에서의 표현식을 해석하고 평가한다.
    • Expression 이라는 추상 클래스를 만드는 경우가 많다.
    • Expression 클래스에서 파생된 구체적인 클래스는 언어의 다양한 규칙 또는 요소를 나타낸다.
  • 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있다.

다이어그램으로 살펴보기

  • 컴포짓 패턴과 굉장히 유사하게 생겼고, 컴포짓 패턴이 그리는 트리인 AST 가 나오게 된다.
  • interpret(Context) 에서 항상 Context 가 있음이 중요하다.
  • TerminalExpression 은 종료가 가능한 Expression 이다.
    • 숫자 연산을 할 때 숫자 그 자체를 들 수 있다.
    • AST 에서 트리의 leaf node 로 표현될 수 있는 것이다.
  • NonTerminalExpression 은 다른 Expression 을 참조하는 Expression 이다.
    • 숫자 연산을 할 때 숫자를 연산하는 기호를 들 수 있다.
    • 그 자체로 끝날 수 없다.

picture 1

예제: Postfix 표기법 (Postfix Notation)

  • 우리가 평소에 수학 문제를 풀 때는 10 - 5 와 같이 부호를 가운데에 두고 양 옆에 숫자를 둔다.
    • 이를 Infix 표기법 (Infix Notation) 이라고 한다.
  • Prefix 표기법에서는 10 5 - 처럼 부호가 뒤에 온다.
  • 얼핏보면 아무런 의미 없어보이지만 자신만의 문법을 만든 것이다.
    • 이렇게 도메인에 특화된 언어를 DSL (도메인 특화 언어) 이라고 한다.

Before: 스택을 이용한 알고리즘으로 구현

  • 스택을 이용해 구현했고 이 방식도 잘 작동한다.
public class PostfixNotation {
    private final String expression;

    public PostfixNotation(String expression) {
        this.expression = 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());
    }
}

After: 문법을 만들어 구현

  • Before 와 정확히 같은 역할을 하는 코드이다.
  • 다만, 개개의 케이스에 어떠한 타입으로 식별될 것인지 자바 코드로 정의했다.
    • VariableExpression 은 이전에 봤던 TerminalExpression 이다.
    • PlusExpression, MinusExpression 은 NonTerminalExpression 이다.
  • 특정 도메인에 특화된 언어처럼 클래스를 설계했다.
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);
    }
}
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);
        }
    }
}
public interface PostfixExpression {
    int interpret(Map<Character, Integer> context);
}
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);
    }
}
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);
    }
}
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);
    }
}

장점

명확한 관심사 분리

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

쉬운 확장

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

가독성

  • 문법과 규칙을 계층구조로 명시적으로 모델링하기 때문에 코드의 가독성이 높아진다.
  • 문법과 요소의 관계를 이해하는데 도움이 된다.

재사용 가능성

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

단점

복잡성

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

성능

  • 종종 재귀와 객체 생성을 포함하므로 규모가 크거나 복잡한 표현식의 경우 오버헤드가 발생한다.
  • 몇몇 사례에서는 인터프리터 패턴이 효율적인 해답이 아닐 수 있다.

제한된 적용 가능성

  • 문법이 너무 자주 진화하는 경우엔 유지 관리하는데에 비용이 더 들 수도 있다.
    • 주로 간단하고 잘 정의된 문법에 효과적이다.

불완전한 솔루션

  • 인터프리터 패턴만으로는 렉싱, 에러 핸들링, 최적화와 같은 케이스의 측면을 처리하기 어려울 수 있다.
  • 경우에 따라 완벽한 솔루션을 얻기 위해 다른 기술이나 도구와 결합해야 할 수도 있다.

자바와 스프링에서 찾아보는 패턴

자바

  • 자바의 컴파일러
  • 정규표현식
public static void main(String[] args) {
        System.out.println(Pattern.matches(".pr...", "spring"));
        System.out.println(Pattern.matches("[a-z]{6}", "spring"));
        System.out.println(Pattern.matches("seo", "jakeseo"));
        System.out.println(Pattern.matches("\\d", "1")); // one digit
        System.out.println(Pattern.matches("\\D", "a")); // one non-digit
    }

스프링

  • 스프링의 SpEL
    • 값을 설정하거나 메서드를 실행하거나 여러가지 역할을 할 수 있다.
public static void main(String[] args) {
    Book book = new Book("spring");
    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression("title");
    System.out.println(expression.getValue(book));
}
@Service
public class MyService implements ApplicationRunner {
    @Value("#{2 + 5}") // SpEL
    private String value;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(value);
    }
}

레퍼런스

  • 백기선님 디자인 패턴 강의
저작자표시 비영리 (새창열림)

'Java > 자바 디자인 패턴' 카테고리의 다른 글

중재자 패턴 (Mediator Pattern) 이란?  (0) 2023.05.09
이터레이터 패턴 (Iterator Pattern) 이란?  (0) 2023.04.30
커맨드 패턴 (Command Pattern) 이란?  (0) 2023.04.28
책임 연쇄 패턴 (Chain Of Responsibility) 이란?  (2) 2023.04.26
프록시 패턴 (Proxy Pattern) 이란?  (0) 2023.04.25
    'Java/자바 디자인 패턴' 카테고리의 다른 글
    • 중재자 패턴 (Mediator Pattern) 이란?
    • 이터레이터 패턴 (Iterator Pattern) 이란?
    • 커맨드 패턴 (Command Pattern) 이란?
    • 책임 연쇄 패턴 (Chain Of Responsibility) 이란?
    Jake Seo
    Jake Seo
    ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바