1. 인터프리터 패턴이란?
- 자주 등장하는 문법을 별개의 간단한 언어로 정의하고 재사용하는 패턴.
- Expression이라는 추상 클래스를 만들어 사용하는 경우가 많음.
- 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있음.
- 트리구조로 인해 컴포지트 패턴과 유사한 형태를 띈다.
2. 인터프리터 패턴을 사용하는 경우
- 특정 언어 또는 문법이 반복되서 사용되는 경우
3. 인터프리터 패턴의 장점
1) 캡슐화 : 문법과 해석을 기본 로직에서 분리하여 별도의 클래스로 캡슐화되므로 모듈화되어 유지보수가 쉬워진다.
2) 쉬운 확장 : Expression 클래스에서 파생된 새로운 구현 클래스만 추가하면 DSL을 쉽게 확장할 수 있다.
3) 가독성 : 문법과 규칙을 계층구조를 이용해 명시적으로 모델링하기 때문에 코드의 가독성이 높아진다.
4) 재사용 가능성 : DSL의 여러 인스턴스에 대해 동일한 표현식 클래스와 인터프리터 로직을 사용할 수 있으므로 재사용성을 촉진한다.
cf) DSL(Domain-Specific Languages) : 도메인 특화 언어
- 특정 분야에 최적화된 프로그래밍 언어
4. 인터프리터 패턴의 단점
1) 복잡성 : DSL이 크고 복잡한 경우 많은 별도의 클래스가 생기므로 관리 및 이해가 어려워질 수 있다.
2) 성능 : 종종 재귀와 객체 생성을 포함하므로 규모가 크거나 복잡한 경우 오버헤드가 발생할 수 있다.
3) 제한된 적용 가능성 : 문법이 너무 자주 진화하는 경우엔 유지 및 관리에 비용이 더 들 수 있다.
4) 불완전한 솔루션 : 인터프리터 패턴만으로는 렉싱, 에러 핸들링, 최적화와 같은 케이스를 처리하기 어려울 수 있다.
5. 코드 구현
1) 구조 - 트리구조
- Context : Expression에서 사용하는 모든 데이터들이 저장되어있는 공간
- Expression : 일련의 규칙을 계산하여 결과값을 반환
- Terminal Expression : Expression을 포함하지 않고, 계산된 결과를 반환
(종료를 포함함 - 더이상 다른 문자로 치환될 수 없는 종점 문자임을 의미)
- Non Terminal Expression : Expression을 참조하여, 종료를 하지않고 다음 규칙으로 넘기는 역할을 함(다른 문자로 치환됨)
2) 소스코드(C#)
- 패턴 적용 전
using System;
using System.Collections.Generic;
namespace StudyCSharp
{
class Program
{
// 기본 코드
private string expression;
static void Main(string[] args)
{
// 이런식의 계산을 앞으로 많이 사용하게 된다? -> 인터프리터 패턴을 이용해 정의해서 사용!
Program p = new Program { expression = "123+-" };
p.calculate();
}
private void calculate()
{
// 후위 표기법을 이용한 계산 로직
Stack<int> stack = new Stack<int>();
foreach (char c in this.expression)
{
switch(c)
{
case '+':
stack.Push(stack.Pop() + stack.Pop());
break;
case '-':
int right = stack.Pop();
int left = stack.Pop();
stack.Push(left - right);
break;
default:
stack.Push(c - '0');
break;
}
}
Console.WriteLine(stack.Pop());
}
}
}
- 패턴 적용 후
using System;
using System.Collections.Generic;
namespace StudyCSharp
{
// Context
// 사용할 데이터들을 담음
class Context
{
public Dictionary<char, int> dictionary = new Dictionary<char, int>();
}
// Expression
// 인터프리터 기능을 추상화한 추상 클래스
abstract class PostfixExpression
{
public abstract int Interpret(Context context);
}
// Terminal Expression
// 인터프리터가 끝나는 부분 -> 또 다른 표현식 아닌 종점(문자)을 반환
class VariableExpression : PostfixExpression
{
public char variable;
public override int Interpret(Context context)
{
return context.dictionary[variable];
}
}
// Non Terminal Expression -> 다음 표현식인 Expression을 반환
class PlusExpression : PostfixExpression
{
public PostfixExpression left;
public PostfixExpression right;
public override int Interpret(Context context)
{
return left.Interpret(context) + right.Interpret(context);
}
}
// Non Terminal Expression
class MinusExpression : PostfixExpression
{
public PostfixExpression left;
public PostfixExpression right;
public override int Interpret(Context context)
{
return left.Interpret(context) - right.Interpret(context);
}
}
// 별도의 Parse 클래스
// 특정 규칙을 가진 입력을 지정된 기능을 수행하는 문법으로 변환
class PostfixParser
{
public static PostfixExpression parse(string expression)
{
Stack<PostfixExpression> stack = new Stack<PostfixExpression>();
foreach(char c in expression)
{
stack.Push(getExpression(c, stack));
}
return stack.Pop();
}
private static PostfixExpression getExpression(char c, Stack<PostfixExpression> stack)
{
switch (c)
{
case '+':
return new PlusExpression() { left = stack.Pop(), right = stack.Pop() };
case '-':
PostfixExpression right = stack.Pop();
PostfixExpression left = stack.Pop();
return new MinusExpression() { left = left, right = right };
default:
return new VariableExpression() { variable = c };
}
}
}
// Client
// 인터프리터로 정의한 특정 문법을 이용해 결과 도출
class Program
{
static void Main(string[] args)
{
// 표현식 입력
PostfixExpression expression = PostfixParser.parse("xyz+-");
// 결과 : x - (y + z)
// 표현식 해석
int result = expression.Interpret(new Context { dictionary = { { 'x', 1 }, { 'y', 2 }, { 'z', 3 } } });
// 결과 : 1 - (2 + 3) = -4
// 출력 : -4
Console.WriteLine(result);
}
}
}
<참고 사이트>