(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다
#1. 객체 생성 관련 패턴 |
#2. 구조 관련 패턴 |
#3. 행동 관련 패턴 |
||
✔️ 인터프리터 패턴이란 (Interpreter Patterns)
자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴
- 컴퓨터 사이언스에서 인터프리터란, 원시 코드 (소스코드)를 코드를 읽는 주체 즉, 기계언어(목적 코드) 로 변환하는 프로그램을 의미합니다.
- 현실에서(?)의 인터프리터는, 통역하는 사람 or 연주자 (악보를 음악으로 변환) 로도 사용된다고 합니다.
- 👏🏻 즉, 인터프리터 패턴의 목적은 자주 사용되는 특정한 문법적 규칙이 존재한다면, 이를 일련의 규칙을 통해 언어/문법으로 규격화하여 해석하는 목적을 가지는 패턴입니다. (ex 정규표현식)
인터프리터 패턴 구조를 짧게 살펴보겠습니다.
- context : Expression 에서 사용하는 모든 데이터들이 저장되어있는 공간입니다.
- Expression : 일련의 규칙을 계산하여 결과값을 반환합니다.
- TerminalExpression : Expression을 포함하지 않고, 계산된 결과를 반환합니다. (종료를 포함함 - 더이상 다른 문자로 치환될 수 없는 종점 문자임을 의미합니다.)
- NonTerminalExpression : Expression 을 참조하여, 종료를 하지않고 다음 규칙으로 값을 넘기는 역할을 합니다. (다른 문자로 치환됨)
→ 인터프리터 패턴은 이 NonTerminalExpression 을 통해 해석한 문자를 다음 해석이 가능한 또다른 Expression으로 넘기면서, TerminalExpression만 남으면, 해석된 마지막 문자를 반환하여 interpreter 로써의 기능을 수행하는 구조를 가집니다.
→ 즉, 트리구조를 가집니다.
기본적인 구조는 이러하니, 이제 컴공시간에만 사용되는 후위 연산자를 인터프리터 패턴을 이용하여 구현해 보겠습니다.
✔️ 인터프리터 패턴 사용 예시
후위 표기법은, 중위 연산자와 다르게 (1 - 3 = -2) 연산자가 나오면 앞의 두 숫자를 연산하는 형식입니다. (1 3 - = -2)
- 다음과 같은 후위 표기법을 연산하는 코드가 존재한다고 할 때, 만약 "123+-" 이란 형식에 대한 연산을 자주 반복해서 사용할 것이라면
- 인터프리터 패턴을 사용하여 이를 "xyz+-" 라는 하나의 형식으로 지정하여 문법으로써 지정할 수 있습니다.
//후위 표현식
@AllArgsConstructor
public class PostfixNotation {
private final String 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());
}
}
- xyz는 그대로 x면 x, y면 y, z면 z를 반환하면 되는 TerminalExpression가 되어 종료를 포함합니다.
- 하지만 "+-"과 같은 Expression은 다른 두개의 Expression을 interpret한 다음 그 결과를 연산하는 Non Termianl Expression 입니다.
✔️ 이를 인터프리터 패턴을 사용하여 구격화된 문법으로 사용하는 코드로 구성해보겠습니다.
📗 PostfixExpression.Class
먼저 인터프리터 기능을 추상화한 Expression 클래스 입니다. 이 인터페이스의 구현체를 각가 TerminalExpression 과 NonTerminalExpression 의 성질을 담은 구현체로 기능을 캡슐화하여 사용합니다.
public interface PostfixExpression {
int interpret(Map<Character, Integer> context);
}
📗 VariableExoression.Class
- TerminalExpression 구조를 가지는 VarialbeExpression 클래스입니다.
- TerminalExpression 이기 때문에, 또다른 문자(표현식) 인 Expression을 반환하지 않고 종점(문자)를 반환합니다.
//Terminal Expression
@AllArgsConstructor
public class VariableExpression implements PostfixExpression {
private Character varialbe;
@Override
public int interpret(Map<Character, Integer> context) {
return context.get(varialbe);
}
}
📗 PlusExpression.Class + MinusExpression.Class
- 다음으로는 Non TerminalExpresiion 구조를 가지는 연산자 클래스입니다.
- 두 후위 표현식의 연산자를 구현하는 클래스는, Non TerminalExpression 이기 때문에 추가로 다음 표현식인 Expression을 반환합니다.
@AllArgsConstructor
public class PlusExpression implements PostfixExpression {
private PostfixExpression left;
private PostfixExpression right;
@Override
public int interpret(Map<Character, Integer> context) {
return left.interpret(context) + right.interpret(context);
}
}
@AllArgsConstructor
public class MinusExpression implements PostfixExpression {
private PostfixExpression left;
private PostfixExpression right;
@Override
public int interpret(Map<Character, Integer> context) {
return left.interpret(context) - right.interpret(context);
}
}
📗 Client 코드와 parser class
- client : 클라이언트는 특정하게 반복되는 형태를 문법으로 만든 expression을 통해 값을 주입하여 결과를 얻습니다.
- parse : Expression을 반환하는 별도의 parser 클래스가 존재하는 이유는, 클라이언트가 입력한 특정 규칙을 지정된 기능(후위 연산)을 수행하는 문법으로 변환하기 위함입니다,
//Client
public class App {
public static void main(String[] args) {
PostfixExpression expression = PostfixParser.parse("xyz+-");
int result = expression.interpret(Map.of('x', 1, 'y', 2, 'z', 3));
System.out.println(result);
}
}
//Parser
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);
}
}
}
그럼 클래스의 다이어그램은 아래와 같아집니다.
실제로 코드를 돌려서 Expression 의 구조를 살펴보면 아래와 같은 Non-Terminal Expression 과 Terminal Expression으로 이루어진 트리 구조로 구성되어집니다.
✔️ Java 와 Spring 에서의 인터프리터
1. 자바의 정규표현식
2. 스프링의 expression language
이렇게 인터프리터 패턴에 대해서도 공부를 해보았지만,, 솔직히 이 패턴을 실무에서 사용하는 일이 있을까.. 싶긴합니다.
팀장님의 경우에는 굳이 사용한다면 복작한 request 데이터를 받을 때 받는 쪽과의 규칙을 정해서 사용할 수 도 있지 않을까~~ 하셨지만
저는! 또 다른 적용 가능한 사례가 생각나지는 않네요!
인터프리터를 패턴을 적용할 기가막힌 상황을 알고계시면 알려주십쇼!
끝!
'Java > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 - 중재자 패턴 (Mediator Pattern) (0) | 2023.04.10 |
---|---|
[디자인 패턴] 행동 패턴 - 이터레이터 패턴 (반복자 패턴 - Iterator Pattern) (0) | 2023.04.04 |
[디자인 패턴] 행동 패턴 - 커맨드 패턴 (Command Pattern) (0) | 2023.03.29 |
[디자인 패턴] 구조 패턴 - 프록시 패턴 (Proxy Pattern) (0) | 2023.03.24 |
[디자인 패턴] 구조 패턴 - 플라이웨이트 패턴 (Flyweight Pattern) (0) | 2023.03.15 |