인터프리터 패턴

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

인터프리터 패턴은 특정 언어의 문법을 나타내는 클래스를 정의하여, 주어진 언어의 문장을 해석하는 방법을 제공하는 디자인 패턴이다. 이 패턴은 UML 클래스 다이어그램을 통해 구조를 나타내며, Context, AbstractExpression, TerminalExpression, NonterminalExpression 등의 구성 요소로 이루어진다. 인터프리터 패턴은 SQL, 통신 프로토콜, 복잡한 비즈니스 규칙 처리, DSL 구현 등 다양한 분야에서 활용될 수 있으며, C#, Java, C++ 등의 프로그래밍 언어를 이용한 예시가 존재한다.

인터프리터 패턴
📚 더 읽어볼만한 페이지
  • 소프트웨어 디자인 패턴 - 모델-뷰-컨트롤러
    모델-뷰-컨트롤러(MVC)는 소프트웨어 디자인 패턴으로, 응용 프로그램을 모델, 뷰, 컨트롤러 세 가지 요소로 분리하여 개발하며, 사용자 인터페이스 개발에서 데이터, 표현 방식, 사용자 입력 처리를 분리해 유지보수성과 확장성을 높이는 데 기여한다.
  • 소프트웨어 디자인 패턴 - 스케줄링 (컴퓨팅)
    스케줄링은 운영 체제가 시스템의 목적과 환경에 맞춰 작업을 관리하는 기법으로, 장기, 중기, 단기 스케줄러를 통해 프로세스를 선택하며, CPU 사용률, 처리량 등을 기준으로 평가하고, FCFS, SJF, RR 등의 알고리즘을 사용한다.

2. 구조

인터프리터 패턴은 `Context`, `AbstractExpression`, `TerminalExpression`, `NonterminalExpression` 등의 클래스로 구성된다.

인터프리터 패턴 UML 클래스 다이어그램
인터프리터 패턴 UML 클래스 다이어그램


UML 클래스 다이어그램에서 `Client` 클래스는 표현식을 해석하기 위해 공통 `AbstractExpression` 인터페이스를 참조한다. `TerminalExpression` 클래스는 자식이 없으며 표현식을 직접 해석한다. `NonterminalExpression` 클래스는 자식 표현식의 컨테이너(`expressions`)를 유지 관리하고 이러한 `expressions`에 해석 요청(`interpret(context)`)을 전달한다.

객체 협업 다이어그램은 런타임 상호 작용을 보여준다. `Client` 객체는 추상 구문 트리에 해석 요청을 보낸다. 이 요청은 트리 구조의 모든 객체로 전달(수행)된다. `NonTerminalExpression` 객체(`ntExpr1, ntExpr2`)는 요청을 자식 표현식으로 전달한다. `TerminalExpression` 객체(`tExpr1, tExpr2, …`)는 해석을 직접 수행한다.

인터프리터 패턴의 구성 요소는 다음과 같다.

👆
좌우로 밀어서 보기
클래스설명
Context해석기가 사용하는 정보를 담고 있다.
AbstractExpression모든 표현식에 공통으로 적용되는 인터페이스를 정의하며, `interpret()` 추상 메서드를 포함한다.
TerminalExpression문법의 종단 기호에 해당하며, `interpret()` 메서드를 구현하여 문장을 해석한다.
NonterminalExpression`AbstractExpression`을 상속받는 클래스 중 하나로, `Interpret()` 메서드는 "Called Nonterminal.Interpret()"를 출력한다.

2.1. UML 클래스와 객체 도표

none
none

--|]]
위의 UML 클래스 다이어그램에서 `Client` 클래스는 표현식을 해석하기 위해 공통 `AbstractExpression` 인터페이스를 참조한다. `interpret(context)`
`TerminalExpression` 클래스는 자식이 없으며 표현식을 직접 해석한다.
`NonTerminalExpression` 클래스는 자식 표현식의 컨테이너(`expressions`)를 유지 관리하고 이러한 `expressions`에 해석 요청을 전달한다.

객체 협업 다이어그램은 런타임 상호 작용을 보여준다. `Client` 객체는 추상 구문 트리에 해석 요청을 보낸다. 이 요청은 트리 구조의 모든 객체로 전달(수행)된다.
`NonTerminalExpression` 객체(`ntExpr1,ntExpr2`)는 요청을 자식 표현식으로 전달한다.
`TerminalExpression` 객체(`tExpr1,tExpr2,…`)는 해석을 직접 수행한다.
none
none

2.2. 구성 요소

인터프리터 패턴은 다음 요소들로 구성된다.

* AbstractExpression (추상 표현식): 모든 표현식에 공통으로 적용되는 인터페이스를 정의하며, `interpret()` 추상 메서드를 포함한다.
* TerminalExpression (종단 표현식): 문법의 종단 기호에 해당하는 표현식 클래스이다. `interpret()` 메서드를 구현하여 실제로 문장을 해석하는 역할을 한다.
* NonterminalExpression (비종단 표현식): `AbstractExpression`을 상속받는 클래스 중 하나이다. `Interpret()` 메서드를 호출하면 "Called Nonterminal.Interpret()"를 출력한다.
* Context (문맥): 해석기가 사용하는 정보를 담고 있는 클래스이다.

👆
좌우로 밀어서 보기
클래스설명C# 예제C++ 예제
Context해석기가 사용하는 정보를 담고 있다.
AbstractExpression모든 표현식에 공통으로 적용되는 인터페이스를 정의하며, `interpret()` 추상 메서드를 포함한다.해당 사항 없음
TerminalExpression문법의 종단 기호에 해당하며, `interpret()` 메서드를 구현하여 문장을 해석한다.해당 사항 없음
NonterminalExpression`AbstractExpression`을 상속받는 클래스 중 하나로, `Interpret()` 메서드는 "Called Nonterminal.Interpret()"를 출력한다.해당 사항 없음

2.2.1. Context

해석기가 사용하는 정보를 담고 있는 클래스이다. C# 코드 예제에서는 `Context` 클래스가 이 역할을 한다. C++ 코드 예제에서는 `Context` 클래스가 `VariableExp` 객체를 키로, `bool` 값을 값으로 갖는 맵을 사용하여 변수에 대한 값을 저장하고 조회하는 기능을 제공한다.

C# 예제:
```csharp
class Context
{
}
```


C++ 예제:
```cpp
class Context {
public:
Context() :m() {}
bool lookup(const VariableExp* key) { return m.at(key); }
void assign(VariableExp* key, bool value) { m[key] = value; }
private:
std::map m;
};
```

2.2.2. AbstractExpression

모든 표현식(Expression)에 공통으로 적용되는 인터페이스를 정의하며, `interpret()` 추상 메서드를 포함한다.

C# 코드 예시:

```csharp
// "AbstractExpression"
abstract class AbstractExpression
{
public abstract void Interpret(Context context);
}

2.2.3. TerminalExpression

csharp
// "TerminalExpression"
class TerminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
Console.WriteLine("Called Terminal.Interpret()");
}
}
```

TerminalExpression은 문법의 종단 기호에 해당하는 표현식 클래스이다. `interpret()` 메서드를 구현하여 실제로 문장을 해석하는 역할을 한다.

2.2.4. NonterminalExpression

csharp
// "NonterminalExpression"
class NonterminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
Console.WriteLine("Called Nonterminal.Interpret()");
}
}
```

NonterminalExpression은 AbstractExpression을 상속받는 클래스 중 하나이다. `Interpret()` 메서드를 호출하면 "Called Nonterminal.Interpret()"를 출력한다.

3. 사용 예시

인터프리터 패턴은 특정 언어의 문법을 정의하고, 해당 언어의 문장을 해석하여 문제를 해결하는 방식이다. 예를 들어, 복잡한 검색 표현식을 처리해야 할 때, 이를 클래스에 직접 구현(하드 코딩)하는 대신 인터프리터 패턴을 사용하면 유연성을 높일 수 있다. 즉, 클래스를 특정 표현식에 고정시키지 않고, 새로운 표현식을 추가하거나 기존 표현식을 변경하는 것이 가능해진다.

인터프리터 패턴은 다음과 같은 경우에 유용하게 사용될 수 있다.

* SQL과 같은 데이터베이스 질의 언어
* 통신 프로토콜을 기술하는 데 사용되는 특수 목적의 컴퓨터 언어

대한민국에서는 소프트웨어 개발의 다양한 영역에서 활용될 수 있는데, 예를 들어 복잡한 비즈니스 규칙을 처리하거나, 특정 도메인의 요구사항을 반영한 DSL을 구현하는 데 사용될 수 있다.

3.1. C# 예제

csharp
// "Context"
class Context
{
}

// "AbstractExpression"
abstract class AbstractExpression
{
public abstract void Interpret(Context context);
}

// "TerminalExpression"
class TerminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
Console.WriteLine("Called Terminal.Interpret()");
}
}

// "NonterminalExpression"
class NonterminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
Console.WriteLine("Called Nonterminal.Interpret()");
}
}

class MainApp
{
static void Main()
{
var context = new Context();

// Usually a tree
var list = new List();

// Populate 'abstract syntax tree'
list.Add(new TerminalExpression());
list.Add(new NonterminalExpression());
list.Add(new TerminalExpression());
list.Add(new TerminalExpression());

// Interpret
foreach (AbstractExpression exp in list)
{
exp.Interpret(context);
}
}
}

3.2. Java 예제

다음은 역폴란드 표기법을 사용하여 범용 프로그래밍 언어가 더 전문화된 언어를 해석하는 Java 예제이다.

입력:
'42 4 2 - +'는 44와 같다.

파서:
```java
import java.util.*;

interface Expression {
void interpret(Stack s);
}

class TerminalExpression_Number implements Expression {
private int number;

public TerminalExpression_Number(int number) { this.number = number; }
public void interpret(Stack s) { s.push(number); }
}

class TerminalExpression_Plus implements Expression {
public void interpret(Stack s) { s.push( s.pop() + s.pop() ); }
}

class TerminalExpression_Minus implements Expression {
public void interpret(Stack s) { s.push( -s.pop() + s.pop() ); }
}

class Parser {
private ArrayList parseTree = new ArrayList(); // 여기에는 하나의 비단말 표현식만 있다.
public Parser(String s) {
for (String token : s.split(" ")) {
if (token.equals("+")) parseTree.add( new TerminalExpression_Plus() );
else if (token.equals("-")) parseTree.add( new TerminalExpression_Minus() );
// ...
else parseTree.add( new TerminalExpression_Number(Integer.valueOf(token)) );
}
}
public int evaluate() {
Stack context = new Stack();
for (Expression e : parseTree) e.interpret(context);
return context.pop();
}
}

class InterpreterExample {
public static void main(String[] args) {
// 여기에서 위 식을 평가한다.
String expression = "42 4 2 - +";
Parser p = new Parser(expression);
System.out.println("'" + expression +"' equals " + p.evaluate());
}
}

3.3. C++ 예제

cpp
#include
#include
#include

class Context;

class BooleanExp {
public:
BooleanExp() = default;
virtual ~BooleanExp() = default;
virtual bool evaluate(Context&) = 0;
virtual BooleanExp* replace(const char*, BooleanExp&) = 0;
virtual BooleanExp* copy() const = 0;
};

class VariableExp;

class Context {
public:
Context() :m() {}
bool lookup(const VariableExp* key) { return m.at(key); }
void assign(VariableExp* key, bool value) { m[key] = value; }
private:
std::map m;
};

class VariableExp : public BooleanExp {
public:
VariableExp(const char* name_) :name(nullptr) {
name = strdup(name_);
}
virtual ~VariableExp() = default;
virtual bool evaluate(Context& aContext) {
return aContext.lookup(this);
}
virtual BooleanExp* replace(const char* name_, BooleanExp& exp) {
if (0 == strcmp(name_, name)) {
return exp.copy();
} else {
return new VariableExp(name);
}
}
virtual BooleanExp* copy() const {
return new VariableExp(name);
}
VariableExp(const VariableExp&) = delete; // rule of three
VariableExp& operator=(const VariableExp&) = delete;
private:
char* name;
};

class AndExp : public BooleanExp {
public:
AndExp(BooleanExp* op1, BooleanExp* op2)
:operand1(nullptr), operand2(nullptr) {
operand1 = op1;
operand2 = op2;
}
virtual ~AndExp() = default;
virtual bool evaluate(Context& aContext) {
return operand1->evaluate(aContext) && operand2->evaluate(aContext);
}
virtual BooleanExp* replace(const char* name_, BooleanExp& exp) {
return new AndExp(
operand1->replace(name_, exp),
operand2->replace(name_, exp)
);
}
virtual BooleanExp* copy() const {
return new AndExp(operand1->copy(), operand2->copy());
}
AndExp(const AndExp&) = delete; // rule of three
AndExp& operator=(const AndExp&) = delete;
private:
BooleanExp* operand1;
BooleanExp* operand2;
};

int main() {
BooleanExp* expression;
Context context;
VariableExp* x = new VariableExp("X");
VariableExp* y = new VariableExp("Y");
expression = new AndExp(x, y);

context.assign(x, false);
context.assign(y, true);
bool result = expression->evaluate(context);
std::cout << result << '\n';

context.assign(x, true);
context.assign(y, true);
result = expression->evaluate(context);
std::cout << result << '\n';
}
```

```text
0
1
```

이 코드는 C++11을 사용하여 논리 표현식을 해석하는 인터프리터 패턴을 구현한 것이다. `BooleanExp` 추상 클래스는 논리 표현식의 기본 인터페이스를 정의한다. `VariableExp` 클래스는 변수를 나타내며, `AndExp` 클래스는 두 논리 표현식의 AND 연산을 나타낸다. `Context` 클래스는 변수와 값의 매핑을 저장하고, `evaluate` 메서드는 주어진 `Context`에서 논리 표현식의 값을 계산한다.

`main` 함수에서는 `X`와 `Y` 두 변수를 생성하고, `AndExp`를 사용하여 이 둘의 AND 연산을 수행하는 `expression`을 만든다. `context` 객체에 `X`는 `false`, `Y`는 `true`를 할당한 후 `expression`을 평가하면 `0`이 출력된다. 이후 `X`를 `true`로 변경하고 다시 평가하면 `1`이 출력된다.

4. 장점 및 고려사항

인터프리터 패턴은 새로운 문법 규칙을 추가하거나 기존 규칙을 변경하기 쉽게 만들어 준다는 장점이 있다. 그러나 문법이 복잡한 언어의 경우에는 클래스 계층 구조가 매우 복잡해질 수 있다는 단점도 있다. 이러한 점을 고려하여, 인터프리터 패턴을 적용할 때 언어의 복잡성과 유지보수의 용이성을 균형 있게 고려해야 한다.