인터프리터 패턴 (Interpreter Pattern)
자주 등장하는 문제를 간단한 언어로 정의하고 재사용한다.
코드에 적용해보기
언어로 정의한다는 말은 약속을 한다는 뜻이다. 예를 들어 1 add 1은 피연산자: 1, 연산자: +, 피연산자: 1로 해석된다. add라는 명령을 사용했을 때 약속된대로 이를 해석하는 패턴이 바로 인터프리터 패턴이다. 대표적인 예시로 정규표현식이 있다. 일종의 패턴을 만들어 \d는 숫자를 의미하고, ^A는 A로 시작한다는 의미이다. 매번 문자열에서 어떤 패턴을 찾는 로직을 만드는 것보단 규칙을 정의하고 이를 해석하는 것이 더 낫다고 판단했기 때문이다.
다만 이를 남발하는 것은 좋지 않다. 인터프리터 패턴은 언어의 문법이 복잡해질수록 관리할 클래스가 많아져 오히려 복잡해진다.
- Context : 모든 Expression에서 사용하는 공통 정보가 존재
- Expression : 표현하는 문법을 나타냄
- TerminalExpression : 종료되는 Expression
- NonterminalExpression : 다른 Expression을 재귀적으로 참조하고 있는 Expression
인터프리터 패턴으로 후위표기법을 구현해보자. 후위표기법은 AB+의 형태로 피연산자를 먼저 표시하고 연산자를 나중에 표시하는 방법이다. 예를 들어 13+는 우리가 알고있는 1 더하기 3이다. 컴파일러가 사용하는 것으로 스택을 사용하는 예시이다.
public interface Expression {
int interpret(Map<Character, Integer> context);
}
후위표기법의 표현 문법을 나타낼 Expression 인터페이스를 생성한다. AB+의 형태에 13+를 대입한 것처럼 Map<Character, Integer> 데이터 구조를 활용하여 A가 1임을 전달할 것이다.
public class PlusExpression implements Expression {
private Expression left;
private Expression right;
public PlusExpression(Expression left, Expression 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 Expression {
private Expression left;
private Expression right;
public MinusExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Map<Character, Integer> context) {
return left.interpret(context) - right.interpret(context);
}
}
Expression 인터페이스를 상속 받은 NonterminalExpression인 PlusExpression, MinusExpression 구상 클래스는 interpret 메소드를 구현한다.
public class VariableExpression implements Expression {
private Character character;
public VariableExpression(Character character) {
this.character = character;
}
@Override
public int interpret(Map<Character, Integer> context) {
return context.get(this.character);
}
}
TerminalExpression인 VariableExpression 클래스는 값을 그대로 반환하는 종료 Expression이다.
public class PostfixParser {
public static Expression parse(String expression) {
Stack<Expression> stack = new Stack<>();
for (char c : expression.toCharArray()) {
stack.push(getExpression(c, stack));
}
return stack.pop();
}
private static Expression getExpression(char c, Stack<Expression> stack) {
switch (c) {
case '+':
return new PlusExpression(stack.pop(), stack.pop());
case '-':
Expression right = stack.pop();
Expression left = stack.pop();
return new MinusExpression(left, right);
default:
return new VariableExpression(c);
}
}
}
후위표기법의 계산 방식대로 파싱해주는 PostfixParser 클래스는 parse() 메소드로 받은 문자열을 character로 하나씩 쪼갠뒤 getExpression() 메소드로 stack에 넣는다.
public class Client {
public static void main(String[] args) {
Expression expression = PostfixParser.parse("xyz+-");
int result = expression.interpret(Map.of('x', 1, 'y', 2, 'z', 3));
System.out.println(result);
}
}
xyz+-는 (z + y) - x 이므로 실행하면 -4라는 결과가 나온다. 디버깅을 통해 구조를 살펴보면 트리 구조를 띄고 있다.
Sql의 실행 로직도 인터프리터 패턴을 통해 구현할 수 있다. 하지만 앞서 말했듯이 규칙이 복잡해질수록 클래스의 개수가 많아지고 Expression, parser도 함께 복잡해지기 때문에 적절한 상황에 사용하는 것이 중요하다.
* 아래의 자료들을 참고하였습니다.
'Programming' 카테고리의 다른 글
Cron 표현식으로 주중 오전 8시 표현하기 (1) | 2024.02.18 |
---|---|
대용량 데이터 처리를 위한 Message Broker (0) | 2023.01.30 |
디자인패턴 시리즈 13. 오브젝트 풀 패턴 (Object Pool Pattern) (2) | 2023.01.27 |
디자인패턴 시리즈 12. 책임 연쇄 패턴 (chain-of-responsibility Pattern) (0) | 2023.01.25 |
디자인패턴 시리즈 11. 프로토타입 패턴 (prototype Pattern) (0) | 2023.01.24 |
댓글