Interpreter Pattern
개요
인터프리터 패턴, 말 그대로 해석을 위한 디자인 패턴이다. 특정 Language(언어)로 작성된 Sentence를 해석할때 쓰인다. 이 때 언어는 어떤 규칙(syntax)으로 표현되어있어야 하고 이 규칙을 일반화하여 해석하는 것이 기본 아이디어.
사용 예
- 정규표현식
- Java의 실행 (JVM Byte 코드를 기계어로 변환하는 과정)
- SQL과 같은 데이터베이스 쿼리언어 해석
구현 아이디어 핵심
- 규칙과 의미있는 기호 패턴에 대한 개별클래스들이 트리구조로 동작하며 해석을 수행한다.
모식도
예제
“46의2진수 46의16진수 16895의2진수 16895의16진수” 와 같은 문자열을 받아 각각의 진법변환 연산을 수행한 결과를 출력하는 코드를 작성해보자
Context
- 해석의 대상이 되는 문자열을 보통 단위별로 끊어서 해석할 수 있도록 전달해주는 역할을 담당한다.
- 일반적으로 Expression의
interpret()
메서드의 인자로 쓰임
class Context {
let string: String
var components = [String]()
init(string: String) {
self.string = string
self.components = string.components(separatedBy: " ")
}
func nextComponent() -> String? {
if self.components.count == 0 {
return nil
} else {
return self.components.removeFirst()
}
}
}
Abstract Expression
- 추상 구문 트리의 모든 Expression들이 채택하는 프로토콜
- interpret 메서드를 기본 정의한다.
protocol Expression {
func interpret(context: Context) -> String
}
다음으로는 Concrete Expression들이며 interpret메서드의 실제 구현을 하게되는 Terminal Exression과 NonTerminal Expression에 대해 알아보자.
Terminal Expression
- 이름에서 알 수 있듯이, 추상 구문 해석 트리의 최말단에 위치하는 Expression이다. 기호나 규칙을 해석해 구체적인 결과값을 return 한다.
class IntToBinary: Expression {
func interpret(context: Context) -> String {
guard let current = context.nextComponent(),
let number = Int(current)
else {
return "해석 실패"
}
let result = String(number, radix: 2)
return result
}
}
class IntToHexaDecimal: Expression {
func interpret(context: Context) -> String {
guard let current = context.nextComponent(),
let number = Int(current)
else {
return "해석 실패"
}
let result = String(number, radix: 16)
return result
}
}
NonTerminal Expression
- Terminal Expression과는 다르게 구체적인 결과값을 리턴하는것이 아니고, 전처리나 규칙판단을 수행한 후에 다른 Expression에게 Context를 넘긴다.
- Interpreter Pattern이 해석작업을 수행하면서 트리구조를 이룰 수 있는 것은 이 NonTerminal Expression 덕분이다. Nonterminal Expression은 다른 Nonterminal 혹은 Terminal Expression의 부모 노드가 될 수 있는 것이다.
class Begin: Expression {
func interpret(context: Context) -> String {
guard let string = context.nextComponent() else { return "종료" }
let expression: Expression
let number = string.components(separatedBy: "의").first!
if string.contains("16진수") {
expression = IntToHexaDecimal()
} else if string.contains("2진수") {
expression = IntToBinary()
} else {
return "해석 실패"
}
return expression.interpret(context: Context(string: number))
+ "\n"
+ self.interpret(context: context)
}
}
이제 실행해보자
class Client {
var context: Context
var expression: Expression?
init(context: Context) {
self.context = context
}
func setExpression(expression: Expression) {
self.expression = expression
}
func analyze() {
guard let expression = self.expression else { return }
print(expression.interpret(context: self.context))
}
}
let context = Context(string: "46의2진수 46의16진수 16895의2진수 16895의16진수")
let client = Client(context: context)
client.setExpression(expression: Begin())
client.analyze()
Client는 context와 expression을 소유하고 있고, 이를 사용해서 구문 해석을 실행하게 된다.
위의 코드는 아래와 같은 결과를 출력한다.
Begin이라는 Nonterminal Expression을 통해 최초의 해석을 실행하게되고 Begin은 전처리와 규칙 판단을 수행해 알맞은 Terminal Expression에게 전달하여 해석이 완료된다.
장점
- 새로운 패턴에 대응하는 Expression을 쉽게 추가할 수 있다.
- 해석 대상이 규칙만 가지고 있다면 적용이 가능하므로 활용범위가 무궁무진하다.
단점
- 하나의 패턴이나 규칙에 1개 이상의 Expression 클래스가 생성되므로 복잡한 문법을 갖고 있는 언어에 대해서는 관리가 쉽지 않다.