종료되는 표현식, 종료되지 않는 표현식(다른 표현식을 참조하는 표현식) 을 바탕으로 섞어서 현 문장을 해석하는 방식으로 진행되어진다.
UML을 보자면 다음과 같은데,
어떠한 해석을 정의하여 특정 값이 왔을 때 정의된 해석에 따라 결과를 도출하는 패턴이라 볼 수 있다.
AbstractExpression : 모든 표현에서 공통적으로 사용할 interpret()를 정의한 추상클래스
TerminalExpression : 표현에서 사용되는 기호들(x,y,z)의 해석값을 구하는 클래스(즉 x일때 무엇, y일때 무엇, z일때 무엇으로 해석하나)
NonterminalExpression : 기호만으로는 결과가 나오지 않고 특정 Expression들을 참조해서 결과를 얻어야하는 클래스(+, -만으로는 결과가 나오지 않고 z, x, y과 같은 TerminalExpression, 또는 또 다른 NontermialExpresion 참조해서 결과값을 얻을 때까지 재귀적으로 참조한다. )
Context : 표현을 분석할 때 사용될 포괄적인 정보
Client : 언어로 정의한 특정 문장을 나타내는 클래스. interpret()를 호출하는 클래스
1. 예시 코드
//AbstractExpression//모든 표현에서 공통적으로 사용하는 메소드 선언publicinterfacePostfixExpression {intinterpret(Map<Character,Integer> context);}//TerminalExpression//특정 기호들에 대한 해석값을 정의publicclassVariableExpressionimplementsPostfixExpression {privateCharacter character;publicVariableExpression(Character character) {this.character= character; } @Overridepublicintinterpret(Map<Character,Integer> context) {returncontext.get(this.character); }}//NonterminalExpression//받은 기호만으로 결과가 나오지 않아 다른 Expression들을 참조하여 결과 도출publicclassPlusExpressionimplementsPostfixExpression {privatePostfixExpression left;privatePostfixExpression right;publicPlusExpression(PostfixExpression left,PostfixExpression right) {this.left= left;this.right= right; } @Overridepublicintinterpret(Map<Character,Integer> context) {returnleft.interpret(context) +right.interpret(context); }}//NonterminalExpression//받은 기호만으로 결과가 나오지 않아 다른 Expression들을 참조하여 결과 도출publicclassMinusExpressionimplementsPostfixExpression {privatePostfixExpression left;privatePostfixExpression right;publicMinusExpression(PostfixExpression left,PostfixExpression right) {this.left= left;this.right= right; } @Overridepublicintinterpret(Map<Character,Integer> context) {returnleft.interpret(context) -right.interpret(context); }}//NonterminalExpression//받은 기호만으로 결과가 나오지 않아 다른 Expression들을 참조하여 결과 도출publicclassMultiplyExpressionimplementsPostfixExpression{privatePostfixExpression left, right;publicMultiplyExpression(PostfixExpression left,PostfixExpression right) {this.left= left;this.right= right; } @Overridepublicintinterpret(Map<Character,Integer> context) {returnleft.interpret(context) *right.interpret(context); }}//Client//받은 문자열을 Expression들을 이용해 파싱하는 리턴 값은 Expression 객체들로 이루어진 Expression을 반환publicclassPostfixParser {publicstaticPostfixExpressionparse(String expression) {Stack<PostfixExpression> stack =newStack<>();for (char c :expression.toCharArray()) {stack.push(getExpression(c, stack)); }returnstack.pop(); }privatestaticPostfixExpressiongetExpression(char c,Stack<PostfixExpression> stack) {switch (c) {case'+':returnnewPlusExpression(stack.pop(),stack.pop());case'-':PostfixExpression right =stack.pop();PostfixExpression left =stack.pop();returnnewMinusExpression(left, right);default:returnnewVariableExpression(c); } }}publicclassApp {publicstaticvoidmain(String[] args) {PostfixExpression expression =PostfixParser.parse("xyz+-a+");//여기서 Map은 Context를 의미int result =expression.interpret(Map.of('x',1,'y',2,'z',3,'a',4));System.out.println(result); }}
결국 이 패턴은 정말 특정 언어(SQL)를 해석할 때에만 크게 효율적인 패턴이라는 것을 알 수 있다.
AST트리 형태로 해석하는 것에 도움을 주는 방식이다.
컴포짓 패턴과 상당히 유사하게 생김. 즉, 이 표현식 방식이 결국 트리구조를 가지게 되는 형태이다.
컴포짓도 컴포넌트안에 컴포넌트가 아래로 계속 존재하게 되고 트리구조처럼 이뤄지는 면에서는 또 인터프리터 패턴이 또 유사한 점이 있다.
컴포짓은 컴포넌트에 재귀형식으로 구현되어있다면, 인터프리터 패턴은 non-terminalExpression이 재귀형식으로 내려가고 Leaf 역할을 terminalExpression이 역할을 대체하는 것 같다.
2. 장점 / 단점
장점
자주 사용하는 패턴을 우리만의 문법으로 정의해서 재사용할 수 있음
기존 코드 변경하지 않고 새로운 Expression 만드는 것이 가능 (OCP)
단점
너무 복잡함
문법이 복잡하면, 구현이 어려움.
구성 가성비가 떨어질수도 있음. 굳이... 이게 정말 필요한지에 대해서 잘 판단해서 구현하는게 좋을 수 있다.