문법 규칙 하나하나를 클래스로 표현해 문장을 해석하는 패턴.
interface Expression { int interpret(Map<String,Integer> ctx); }
class Number implements Expression {
private final int v;
public int interpret(Map<String,Integer> c) { return v; }
}
class Add implements Expression {
private final Expression l, r;
public int interpret(Map<String,Integer> c) {
return l.interpret(c) + r.interpret(c); // 재귀 해석
}
}
// (1 + 2) 를 트리로 만들어 해석한다
new Add(new Number(1), new Number(2)).interpret(ctx); // 3
Composite와의 관계
Interpreter 의 구문 트리는 사실상 Composite 구조다 단말 표현식(Number) = Leaf
- 비단말 표현식(Add) — = Composite
차이는 목적이다 — Composite 는 '부분-전체를 같게 다루기', Interpreter 는 '문법을 해석하기'
언제 쓰나 — 좁다
-
적합 — 문법이 단순하고 안정적이다 (규칙 수십 개 이하)
- 예) 검색 필터 식, 권한 규칙, 수식 계산기
-
부적합 — 문법이 복잡하다
- 클래스 수가 규칙 수에 비례해 폭발한다
- ANTLR·JavaCC 같은 파서 생성기를 쓴다
GoF 23개 중 실무에서 직접 구현하는 일이 가장 드문 패턴이다. 다만 정규식·SQL·SpEL처럼 이미 만들어진 인터프리터를 쓰는 경우는 매우 흔하다.
실무에서
java.util.regex.Pattern // 정규식 해석
Spring Expression Language // #{...} 평가
java.text.MessageFormat
면접 함정
- ❌ "복잡한 언어 파싱에 쓴다" → 오히려 복잡하면 파서 생성기를 쓴다.
- ❌ Visitor와 무관하다고 답변 → 구문 트리에 연산을 추가할 때 Visitor를 함께 쓴다.
왜 직접 구현이 드문가
문법 규칙 하나 = 클래스 하나
- 사칙연산만 해도 Number·Add·Sub·Mul·Div·Paren...
게다가 파싱(문자열 → 트리)은 이 패턴이 다루지 않는다
- Interpreter 는 '이미 만들어진 트리의 해석' 만 담당한다
그래도 값을 하는 자리
사용자가 규칙을 입력하는 기능
- 알림 조건식 "금액 > 10000 AND 지역 = '서울'" · 할인 규칙 · 검색 필터
규칙을 하드코딩하면 배포 없이 못 바꾼다 표현식으로 저장해 해석하면 데이터만 바꾸면 된다
대안을 먼저 본다
Spring Expression Language(SpEL) · 규칙 엔진(Drools) · 파서 생성기(ANTLR) 스크립트 엔진(JSR-223) — 다만 임의 코드 실행 위험
보안 주의
사용자 입력을 해석하는 순간 공격면이 생긴다 — 깊은 중첩으로 스택 오버플로, 반복 폭발로 CPU 소진. 깊이 제한·평가 시간 제한·허용 연산 화이트리스트가 필요하다.