본문 바로가기

Dev.../패턴자료

The Interpreter Pattern

The Interpreter Pattern

    몇몇 프로그램들은 그것들이 수행할 수 있는 연산자들을 설명하는 언어를  갖는 것으로부터 이점을 얻는다. Interpreter 패턴은 일반적으로 언어에 대한 문법 정의를 묘사하고 언어에서 문장들을 해석하는 문법 사용을 묘사한다.

Motivation

프로그램이  다른 것들을 나타내지만 몇 가지는 유사한 경우가 있을 때 Interpreter 패턴은 이러한 경우들을 묘사하는 간단한 언어를 이용할 수 있는 이점이 있고, 언어를 해석하는 프로그램을 갖는다.

    문제중의 하나는 우리가 다루어야 할 것이 언어가 도움을 줄 수 있을 때를 어떻게 인식하는가 이다. 매크로 언어의 리코더는 단순히 후에 재생되기 위한 메뉴와 키스트로크 연산자들을 기록한다.

Applicability

Interpreter 가 어디에서 도움을 줄 수 있는지를 인식하는 것은 어려운 문제이고, 프로그래머들은 형식적인 언어/컴파일러 연습 없이 종종 이러한 접근을 간과한다. Interpreter 패턴이 사용되는 경우가 많지는 않지만, 언어들이 이용할 수 있는 두 가지의 일반적인 경우가 있다.
  1. 프로그램이 대수적인 문자열을 해석해야만 할 때. 이 경우는 명확한 경우이다. 프로그램은 사용자가 입력한 어떤 부류의 식이 있는 계산에 기인한 연산자들을 수행하도록 요청된다. 이것은 종종 수리적인 그래픽 프로그램에서 발생한다.
     
  2. 프로그램이 다양한 종류의 결과를 만들어 내야 할 때. 이 경우는 약간 명확하지 않지만, 좀더 유용하다. 정렬되지 않는 데이터의 열들을 나타내고 다양하게 정렬할 수 있는 프로그램을 고려해 보자. 이러한 프로그램은 종종 보고서 생성기로 참조되어지고, 원래 데이터는 관계 형 데이터베이스에 저장되어 있다. 보고서 작성 프로그램에 대한 사용자 인터페이스는 일반적으로 SQL 보다 훨씬 간단하다. 사실 몇 가지 경우에서 간단한 보고서 언어는 보고서 프로그램으로 해석되어 질 수 있고 SQL로 변환될 수 있다.

Sample Code

테이블에서 5개의 데이터 열을  연산하고 이러한 데이터로 다양한 보고서들을 반환하는 보고서 생성기를 단순화하여 고려해 보자. 우리가 어떤 수영 대회에서 다음의 정렬된 결과를 가지고 있다고 해보자. 
 
First NameLast NameAgeClubTime
AmandaMcCarthy12WCA29.28
JamieFalco12HNHS29.80
MeaghanO'Donnell12EDST30.00
GreerGibbs12CDEV30.04
RhiannonJeffrey11WYW30.04
SophieConnolly12WAC30.05
Dana Helyer12ARAC30.18
51명 수영선수들의 결과를 얻었다면 우리는 클럽, 성이나 나이에 따라 결과를 정렬하는 것이 편리할지도 모른다는 것을 인식할 것이다.

    우리는 정렬의 재귀적이지 않는 문법을 정의할 것이다.

Print lname frname club time sortby club thenby time

    이 예제에서는 아래의 단어들처럼 3개의 동사를 정의하였다.

Print
Sortby
Thenby

    그리고 5개의 열 이름이 나열되었다.
Frname
Lname
Age
Club
Time

    편리성을 위하여 우리는 언어는 사건에 둔감하다고 가정하자. 또 이 언어의 간단한 문법은 구두점이 없다는 것이다. 그리고 결과적으로 다음처럼 된다.

    Print var[var] [sortby var [thenby var]]

마지막으로 유일한 주 동사가 있고 반면에 각각의 문장이 선언되어진다. 이 문법에서는 문장 할당이나 계산 능력이 없다.

Interpreting the Language

인터프리팅 언어는 3개의 단계로 이루어진다.
  1. 토큰(token)으로 언어의 기호를 해석한다.
  2. 토큰들을 액션(action)으로 분해한다.
  3. 액션을 실행한다.

우리는 StringTokenizer를 가지고 각각의 문장을 읽고 각각의 단어에 대해 어떤 수로 대체해서 언어를 토큰으로 해석한다. 일반적으로 파서들은 분해된 토큰을 하나의 스택에 넣는다. 우리는 Stack 클래스를 벡터를 사용하여 구현하였다. Stack 클래스는 스택 내용을 조사하고 쉽게 다루기 위해 push, pop, top 그리고 nextTop 메소드를 가지고 있다.

    파싱후에 우리의 스택은 아래와 같이 보여질 수 있다:

TypeToken  
VarTime← top of Stack
VerbThenby
varClub
VerbSortby
VarTime
VarFrname
VerbLname

     그러나, 우리는 "verb" thenby 는 다른 것보다 실제적인 의미가 없다는 것을  알 수 있다. 우리의 초기 스택은 다음처럼 보여진다.

Time
Club
Sortby
Time
Club
Frname
Lname
Print

Objects Used in Parsing

실제적으로 우리는 스택에 수리적인 토큰은 넣지 않지만, ParseObject 타입과 값을 갖는다 
public class ParseObject {	public static final int VERB=1000, VAR = 1010, MULTVAR = 1020;	protected int value;	protected int type;		public int getValue() {return value;}	public int getType() {return type;}}
이러한 객체들은 VERB 나 VAR 타입을 가질 수 있다. 그리고 나서 우리는 이 객체를 ParseVerb 와 ParseVar 객체로 확장한다.

    여기에 간단한 계층구조가 있다:

       

    파싱 프로세서는 StringTokenizer 와 파서 객체들을 이용하여 아래처럼 코드를 작성할 수 있다.
public Parser(String line) {	stk = new Stack();	actionList = new Vector();		StringTokenizer tok = new StringTokenizer(line);		while(tok.hasMoreElements()) {		ParseObject token = tokenize(tok.nextToken());		if(token != null)			stk.push(token);	}}           //----------------------------------------private ParseObject tokenize(String s) {	ParseObject obj = getVerb(s);	if (obj == null)		obj = getVar(s);	return obj;}//----------------------------------------private ParseVerb getVerb(String s) {	ParseVerb v;	v = new ParseVerb(s);	if (v.isLegal()) 		return v.getVerb(s);	else		return null;}//----------------------------------------private ParseVar getVar(String s) {	ParseVar v;	v = new ParseVar(s);	if (v.isLegal()) 		return v;	else		return null;}   
ParseVerb 와 ParseVar 클래스는 단어가 인식되었다면 'true'가 되어 객체들을 반환한다.
public class ParseVerb extends ParseObject {	static public final int PRINT=100, SORTBY=110, THENBY=120;	protected Vector args;	   	public ParseVerb(String s) {		args = new Vector();		s = s.toLowerCase();		value = -1;		type = VERB;		if (s.equals("print")) value = PRINT;		if (s.equals("sortby")) value = SORTBY;	}

Reducing the Parsed Stack

    스택에 대한 토큰은 다음과 같은 형식을 갖는다.

Var
Var
Verb
Var
Var
Var
Var
Verb


    우리는 아규먼트들이 동사 객체를 포함할 때 까지 MultVar 클래스로 연속적인 변수들을 포개어서 동시에 토큰에 대한 스택을 줄일 수 있다.



스택이 동사로 축소되었을 때, 이 동사와 그것의 아규먼트는 액션 리스트에 있다; 액션이 실행되었을 때 스택이 비게 된다.

    이 전체적인 프로세스는 사용자 인터페이스에서 Go 버튼을 눌렀을 때 실행하는 Command 객체가 있는 Parse 클래스를 생성하여 수행할 수 있다 :
	public void actionPerformed(ActionEvent e) {		Parser p =  new Parser(tx.getText());		p.setData(kdata, ptable);		p.Execute();	}
파서 자체가 위에서 본 것처럼 토큰들을 축소한다. 그것은 스택에서 여러 가지 토큰의 쌍을 체크하고, 각각 다른 다섯 가지의 경우에 대해 각 쌍을 하나의 쌍으로 축소한다.
	//executes parse of command line	public void Execute() {		while(stk.hasMoreElements()) {			if(topStack(ParseObject.VAR, ParseObject.VAR)) {				//reduce (Var Var) to Multvar				ParseVar v = (ParseVar)stk.pop();				ParseVar v1 = (ParseVar)stk.pop();				MultVar mv = new MultVar(v1, v);				stk.push(mv);			}				//reduce MULTVAR VAR to MULTVAR			if(topStack(ParseObject.MULTVAR, ParseObject.VAR)) {				MultVar mv =  new MultVar();				MultVar mvo = (MultVar)stk.pop();				ParseVar v = (ParseVar)stk.pop();				mv.add(v);				Vector mvec = mvo.getVector();				for (int i = 0; i< mvec.size(); i++)				mv.add((ParseVar)mvec.elementAt(i));				stk.push(mv);			}						//reduce (Multvar Var) to Multvar			if(topStack(ParseObject.VAR, ParseObject.MULTVAR)) {				ParseVar v = (ParseVar)stk.pop();				MultVar mv = (MultVar)stk.pop();				mv.add(v);				stk.push(mv);			}						//reduce Verb Var to Verb containing vars			if (topStack(ParseObject.VAR, ParseObject.VERB)) {				addArgsToVerb();			}						//reduce Verb MultVar to Verb containing vars			if (topStack(ParseObject.MULTVAR, ParseObject.VERB)) {				addArgsToVerb();			}						//move top verb to action list			if(stk.top().getType() == ParseObject.VERB) {				actionList.addElement(stk.pop());			}					}//while				//now execute the verbs		for (int i =0; i< actionList.size() ; i++) {			Verb v = (Verb)actionList.elementAt(i);			v.setData(data, ptable);			v.Execute();		}	}
우리는 또한 Print 와 Sort Verb 클래스들을 Command 객체들을 만들어 열거된 액션 목록을 하나씩 실행한다.

    최종적인 프로그램은 아래 그림과 같다.

       

Consequences of the Interpreter Pattern

    프로그램에서 인터프리터를 소개할 때마다 사용자가 언어에서 명령들을 입력하는 프로그램을 위해 간단한 방법을 제공할 필요가 있다. 앞에서 언급했던 것처럼 Macro를 기록하는 방법이 있고 위의 프로그램에서처럼 텍스트 필드를 만들어 편집할 수 있게 하는 방법이 있다.

    그러나 언어와 그에 동반된 문법을 수용하는 것은 또한 잘못 입력된 항목이나 문법적으로 잘못 놓여진 원소에 대한 꾀나 광대한 에러 체크를 요하게 된다. 사용자가 이런 에러들을 인식하기 위한 좀더 효율적인 방법은 설계하고 구현하기가 쉽지 않다.

    위의 인터프리터 예제에서는 유일한 에러 핸들링은 인식되지 않는 키워드는  ParseObject로 변환되지 않는다는 것이고 스택에 추가하지 않는다는 것이다. 그러므로, 아무일도 발생하지 않는데 결과 스택 순서가 성공적으로 해석될 수 없기 때문이다.

    또, 라디오 버튼이나 명령버튼 과 리스트 박스의 사용자 인터페이스에서 자동으로 언어가 생성되는 것을 고려해야 한다. 반면에 그러한 인터페이스를 사용하여 언어에 대한 필요성이 없어지는 것처럼 보일 수 있지만 같은 시퀀스의 요구나 계산은 여전히 적용된다. 우리가 순차적인 연산의 순서를 정하는 방법을 가지고 있어야만 할 때 그 언어가 사용자 인터페이스로부터 생성된다 할 지라도 언어는 그렇게 하는 좋은 방법이다.

    인터프리터 패턴은 일단 일반적인 파싱과 리덕션 도구들을 만들었으면  쉽게 확장하거나 문법을 수정할 수 있는 이점을 가지고 있다.일단 기초가 구성되면 쉽게 새로운 동사나 변수를 추가할 수 도 있다. 

    Parse 클래스에서 보았던 해석 구조에서 단지 6가지 경우만 고려했고 문장 자체가 간단했다. 우리가 좀더 확장을 할 때 디자인 패턴은 그것들 각각에 대한 클래스를 생성할 것을 제안했다. 이것은 다시 언어가 쉽게 확장될 수 있게 하지만 유사한 작은 클래스들의 양을 증가시키는 단점을 가지고 있다.

    마지막으로 문법이 더욱 복잡해져 감에 따라 프로그램을 유지하는 것이 어려워 질 수 있다.

    반면에 인터프리터들은 일반적인 프로그래밍 문제들을 푸는데 있어서 공통적인 것은 아니다. 다음 장에 나오는 Iterator 패턴이 사용할 수 있는 가장 공통적인 것이다.

'Dev... > 패턴자료' 카테고리의 다른 글

The Command Pattern  (0) 2005.03.01
The Iterator Pattern  (0) 2005.03.01
The Strategy Pattern  (0) 2005.03.01
The Template Pattern  (0) 2005.03.01
The Mediator Pattern  (0) 2005.03.01