DesignPatternSmalltalkCompanion:Interpreter: Difference between revisions

From 흡혈양파의 번역工房
Jump to navigation Jump to search
(DPSC INTEARPRETER 페이지 추가)
 
(큰따옴표 수정)
 
Line 17: Line 17:
====도메인 아키텍처와 해석자====
====도메인 아키텍처와 해석자====


Interpreter 패턴은 분류하기가 힘든 패턴이다. “해석이 필요한 언어가 존재하거나 추상 구문 트리로 언어에 정의된 문장을 표현하고자 한다면” Interpreter 패턴을 사용할 수 있다 (DP 245). Interpreter 패턴을 적용 시 가장 까다로운 점 중 하나는 당신이 해석할 언어를 가지고 있는지 인식하는 것이다. 개발자가 컴파일러 구성을 정식으로 훈련받지 않은 이상 이 패턴을 적용 시 발생할 많은 문제들에 대해 해결책이 될 수 없음을 발견했다.  
Interpreter 패턴은 분류하기가 힘든 패턴이다. "해석이 필요한 언어가 존재하거나 추상 구문 트리로 언어에 정의된 문장을 표현하고자 한다면" Interpreter 패턴을 사용할 수 있다 (DP 245). Interpreter 패턴을 적용 시 가장 까다로운 점 중 하나는 당신이 해석할 언어를 가지고 있는지 인식하는 것이다. 개발자가 컴파일러 구성을 정식으로 훈련받지 않은 이상 이 패턴을 적용 시 발생할 많은 문제들에 대해 해결책이 될 수 없음을 발견했다.  


반면 특정 문제 도메인을 그 언어로 나타낼 수 있음을 인식한 경우에는 전체 시스템의 아키텍쳐에서 핵심을 형성할 수 있다. 확실히 이는 일반 패턴과 애플리케이션 혹은 도메인 특정적 패턴 사이의 어중간한 영역에 위치한다. 패턴과 그 활용의 이해에 대해 투자해야 할 것이 많아 보일지는 모르나 그로 인한 잠재적 보상은 어마어마하다.  
반면 특정 문제 도메인을 그 언어로 나타낼 수 있음을 인식한 경우에는 전체 시스템의 아키텍쳐에서 핵심을 형성할 수 있다. 확실히 이는 일반 패턴과 애플리케이션 혹은 도메인 특정적 패턴 사이의 어중간한 영역에 위치한다. 패턴과 그 활용의 이해에 대해 투자해야 할 것이 많아 보일지는 모르나 그로 인한 잠재적 보상은 어마어마하다.  
Line 39: Line 39:
Interpreter 패턴은 자신만의 작은 스크립팅 툴을 개발하고자 하는 개발자들에게 안내서를 제공하지만 문제해결로 제한하진 않는다. 위에서 언급하였듯 Interpreter 패턴을 적용 시 곤란한 점들 중 하나는 당신이 해석자를 쓸 수 있는 언어를 갖고 있는지 인지하는 것이다. 문제가 되는 언어를 확인한 후 파스 노드의 클래스 계층구조를 구축하여 언어를 나타내는 것이 Interpreter 패턴의 핵심이다.  
Interpreter 패턴은 자신만의 작은 스크립팅 툴을 개발하고자 하는 개발자들에게 안내서를 제공하지만 문제해결로 제한하진 않는다. 위에서 언급하였듯 Interpreter 패턴을 적용 시 곤란한 점들 중 하나는 당신이 해석자를 쓸 수 있는 언어를 갖고 있는지 인지하는 것이다. 문제가 되는 언어를 확인한 후 파스 노드의 클래스 계층구조를 구축하여 언어를 나타내는 것이 Interpreter 패턴의 핵심이다.  


[디자인 패턴] 편에서는 Interpreter 패턴이 두 가지 타입의 언어, 정규 표현식과 불 연산식에 적용됨을 보여준다. 이 단순한 언어는 훌륭한 예제가 되긴 하지만 [디자인 패턴]의 많은 독자들은 다른 타입의 언어에 이 패턴을 적용시킨 사례를 보지 못할 것이다. “새로운 시야로 바라보고” 도메인 특정적 언어와 관련된 여러 종류의 문제를 표현할 수 있음을 이해하는 것은 숙련된 설계자들에게 나타나는 특징 중 하나이다.  
[디자인 패턴] 편에서는 Interpreter 패턴이 두 가지 타입의 언어, 정규 표현식과 불 연산식에 적용됨을 보여준다. 이 단순한 언어는 훌륭한 예제가 되긴 하지만 [디자인 패턴]의 많은 독자들은 다른 타입의 언어에 이 패턴을 적용시킨 사례를 보지 못할 것이다. "새로운 시야로 바라보고" 도메인 특정적 언어와 관련된 여러 종류의 문제를 표현할 수 있음을 이해하는 것은 숙련된 설계자들에게 나타나는 특징 중 하나이다.  


====파서====
====파서====
Line 48: Line 48:
# 그 언어로 기술된 문장을 해석하는 기법을 표현한다.  
# 그 언어로 기술된 문장을 해석하는 기법을 표현한다.  


앞서 언급하였듯, Interpreter 패턴을 사용하는 프로그램은 고수준의 표현식을 기본적 표현으로서 파싱하는 과정을 수반한다. 하지만 파서 자체는 파싱 후 표현식을 해석하거나 평가하지 않기 때문에 Interpreter로 “취급하지 않는다.이것이 바로 Interpreter 패턴과 관련된 일반적인 오해이다. 그럼에도 불구하고 파서들은 고수준 표현식을 기본적 형태로 나타내기 위해 Interpreter 패턴의 것과 동일한ㅡ또는 완전히 동일한ㅡ클래스와 구조를 사용한다.  
앞서 언급하였듯, Interpreter 패턴을 사용하는 프로그램은 고수준의 표현식을 기본적 표현으로서 파싱하는 과정을 수반한다. 하지만 파서 자체는 파싱 후 표현식을 해석하거나 평가하지 않기 때문에 Interpreter로 "취급하지 않는다." 이것이 바로 Interpreter 패턴과 관련된 일반적인 오해이다. 그럼에도 불구하고 파서들은 고수준 표현식을 기본적 형태로 나타내기 위해 Interpreter 패턴의 것과 동일한ㅡ또는 완전히 동일한ㅡ클래스와 구조를 사용한다.  


물론 다양한 스몰토크 컴파일러들이 스몰토크 표현식을 저수준 트리 구조로 분해하기 위해 파서를 포함하기도 한다. 그 외 많은 스몰토크 시스템들도 Interpreter와 동일한 클래스 구조를 사용하는 파서를 포함한다; 이 중 몇 가지를 여기서 언급하고자 한다. TGen 파서 생성 프로그램은 UIUC Smalltalk Archives에서 무료로 사용할 수 있다.<ref name="주석5장1">UIUC (일리노이대학교 어바나-샴페인 캠퍼스) Smalltalk archives는 Ralph Johnson과 그의 대학원생들이 관리하며, 여러 방언들에 대한 스몰토크 코드를 무료로 제공한다. http://st-www.cs.uiuc.edu/ 를 통해 이용할 수 있다.</ref> Outback Software의 Dave Collins는 Perl, Lex, 그리고 다른 언어들의 문법으로 쓰인 표현식을 이해하는 RegExp란 이름의 정규 표현식 컴파일러-인식기를 발전시켰다. ParCE(Alpert & Rosson, 1992)는 context-free 파서로서, 자연 언어로 된 문장들을 비단말적 문장성분과 (명사구, 전치사구, 동사구 등) 단말적 문장성분으로 (명사, 동사, 전치사, 한정사 등) 구성된 기본 트리 구조로 분해하기 위한 목적으로 만들어졌다.
물론 다양한 스몰토크 컴파일러들이 스몰토크 표현식을 저수준 트리 구조로 분해하기 위해 파서를 포함하기도 한다. 그 외 많은 스몰토크 시스템들도 Interpreter와 동일한 클래스 구조를 사용하는 파서를 포함한다; 이 중 몇 가지를 여기서 언급하고자 한다. TGen 파서 생성 프로그램은 UIUC Smalltalk Archives에서 무료로 사용할 수 있다.<ref name="주석5장1">UIUC (일리노이대학교 어바나-샴페인 캠퍼스) Smalltalk archives는 Ralph Johnson과 그의 대학원생들이 관리하며, 여러 방언들에 대한 스몰토크 코드를 무료로 제공한다. http://st-www.cs.uiuc.edu/ 를 통해 이용할 수 있다.</ref> Outback Software의 Dave Collins는 Perl, Lex, 그리고 다른 언어들의 문법으로 쓰인 표현식을 이해하는 RegExp란 이름의 정규 표현식 컴파일러-인식기를 발전시켰다. ParCE(Alpert & Rosson, 1992)는 context-free 파서로서, 자연 언어로 된 문장들을 비단말적 문장성분과 (명사구, 전치사구, 동사구 등) 단말적 문장성분으로 (명사, 동사, 전치사, 한정사 등) 구성된 기본 트리 구조로 분해하기 위한 목적으로 만들어졌다.
Line 92: Line 92:
[[image:dpsc_chapter05_Interpreter_02.png]]
[[image:dpsc_chapter05_Interpreter_02.png]]


클래스 QueryNode는 메시지 evaluateAgainst: 를 정의한다; 모든 구체적 서브클래스는 이 메시지를 오버라이드해야 한다. 당신의 쿼리 언어를 이용해 구성된 문장이나 표현식으로부터 런타임 시 파스 트리를 구성하여 파스 트리로 표현된 쿼리를 평가(또는 해석)하는 것이 기본적인 생각이다. 우리 예제에서 해석 파스는 “이 특정 컨텍스트에 네가 무슨 값을 가지는지 가서 확인해보라”라는 의미를 가진 evaluateAgainst: 메시지를 파스 트리의 루트 노드로 전송함으로써 처리된다. 이를 더 자세히 이해하기 위해 위의 다이어그램에 나타나는 3개의 클래스의 구현을 살펴보고자 한다:
클래스 QueryNode는 메시지 evaluateAgainst: 를 정의한다; 모든 구체적 서브클래스는 이 메시지를 오버라이드해야 한다. 당신의 쿼리 언어를 이용해 구성된 문장이나 표현식으로부터 런타임 시 파스 트리를 구성하여 파스 트리로 표현된 쿼리를 평가(또는 해석)하는 것이 기본적인 생각이다. 우리 예제에서 해석 파스는 "이 특정 컨텍스트에 네가 무슨 값을 가지는지 가서 확인해보라"라는 의미를 가진 evaluateAgainst: 메시지를 파스 트리의 루트 노드로 전송함으로써 처리된다. 이를 더 자세히 이해하기 위해 위의 다이어그램에 나타나는 3개의 클래스의 구현을 살펴보고자 한다:


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 254: Line 254:
</syntaxhighlight>
</syntaxhighlight>


이 메소드는 사실상 노드로 이루어진 파스 트리를 구성한다. 가용성 달력에게 스스로를 평가하도록 요청하면 재미있는 부분이 발생한다. 이번 경우, 컴포넌트 부분에서 가용성 달력을 구성함을 의미한다. ANDedCalendar와 ORedCalendar는 이 구성에서 서로 다른 작업을 수행한다. ORedCalendar는 좌측과 우측 달력에서 자원에 대한 concatenation을 리턴할 것이다. 반대로 ANDedCalandar는 좌측과 우측 달력의 결과를, 오른쪽에 가능한 시간 슬롯을 왼쪽의 것과 순열(permutation)시킨 결과에 결합한다. 따라서 최상위 단계에는 ResourceScheduler가 노동자, 기계, 도구의 모든 가능한 조합의 집합을 수신하며, 그것의 규칙들은 “최고의” 조합을 평가할 수 있다.
이 메소드는 사실상 노드로 이루어진 파스 트리를 구성한다. 가용성 달력에게 스스로를 평가하도록 요청하면 재미있는 부분이 발생한다. 이번 경우, 컴포넌트 부분에서 가용성 달력을 구성함을 의미한다. ANDedCalendar와 ORedCalendar는 이 구성에서 서로 다른 작업을 수행한다. ORedCalendar는 좌측과 우측 달력에서 자원에 대한 concatenation을 리턴할 것이다. 반대로 ANDedCalandar는 좌측과 우측 달력의 결과를, 오른쪽에 가능한 시간 슬롯을 왼쪽의 것과 순열(permutation)시킨 결과에 결합한다. 따라서 최상위 단계에는 ResourceScheduler가 노동자, 기계, 도구의 모든 가능한 조합의 집합을 수신하며, 그것의 규칙들은 "최고의" 조합을 평가할 수 있다.


이 해법의 구조는 불필요한 경로를 제거함으로써 불필요한 공간을 쳐내기 쉽게 만든다. 예를 들어, 우리 예제에서 하나의 도구가 이미 일주일 내내 예약되어 있다면 그 달력을 포함하는 ORedCalendar는 이러한 사실을 감지하고 그것을 concatenation에 포함되는 것을 거부할 수 있다.  
이 해법의 구조는 불필요한 경로를 제거함으로써 불필요한 공간을 쳐내기 쉽게 만든다. 예를 들어, 우리 예제에서 하나의 도구가 이미 일주일 내내 예약되어 있다면 그 달력을 포함하는 ORedCalendar는 이러한 사실을 감지하고 그것을 concatenation에 포함되는 것을 거부할 수 있다.  
Line 272: Line 272:
이것이 실제로 Interpreter 패턴의 사용인지에 대해서는 아직도 토론이 필요하다. 단, 한 가지 빠뜨린 핵심 요소는 파싱된 실제 언어가 없다는 것이다. 대신 UI Builder에서 구축된 비주얼웍스 윈도우 창의 복합 구조가 이후 재구성 목적을 위해 WindowSpec 메소드에 Strings의 배열로 저장된다.
이것이 실제로 Interpreter 패턴의 사용인지에 대해서는 아직도 토론이 필요하다. 단, 한 가지 빠뜨린 핵심 요소는 파싱된 실제 언어가 없다는 것이다. 대신 UI Builder에서 구축된 비주얼웍스 윈도우 창의 복합 구조가 이후 재구성 목적을 위해 WindowSpec 메소드에 Strings의 배열로 저장된다.


여기서는 UISpecifications에게 그의 표현들을 구축도록 요청하는 방식이 Interprefer 또는 Composite 패턴을 결정짓는가라는 질문이 제기된다. 둘 중 어떤 패턴을 사용해도 괜찮다. 당신이 배열을 “언어” 부류로 간주한다면, 이는 Interpreter 패턴의 정의와 일치한다. 반면 언어라고 생각되지 않는다면 Interpreter 패턴의 정의와 맞지 않으며, 대신 Composite 패턴의 사용으로 간주해야 한다.
여기서는 UISpecifications에게 그의 표현들을 구축도록 요청하는 방식이 Interprefer 또는 Composite 패턴을 결정짓는가라는 질문이 제기된다. 둘 중 어떤 패턴을 사용해도 괜찮다. 당신이 배열을 "언어" 부류로 간주한다면, 이는 Interpreter 패턴의 정의와 일치한다. 반면 언어라고 생각되지 않는다면 Interpreter 패턴의 정의와 맞지 않으며, 대신 Composite 패턴의 사용으로 간주해야 한다.


==Notes==
==Notes==

Latest revision as of 07:38, 20 January 2013

INTERPRETER (DP 243)

의도

언어에 따라서 문법에 대한 표현을 정의하고, 그 언어로 문장을 해석하기 위해 정의한 표현에 기반하여 분석기를 정의한다.

구조

Dpsc chapter05 Interpreter 01.png

논의

Interpreter 패턴은 단순한 언어를 위해 문법을 정의하고, 그 언어로 표현식을 나타내며, 이러한 표현식을 해석 또는 평가하는 일을 다룬다. Interpreter 패턴은 런타임 시 컴파일러의 사용이 필요한 것으로 보일 때 유용하다. 여기에는 런타임 시ㅡ컴파일 시간에ㅡ구성된 임의의 스몰토크 표현식에 대한 평가도 포함된다. 물론 각 스몰토크 환경은 컴파일러를 구성하는 클래스들을 포함하고 있으며 개발 환경에서 완전히 이용할 수 있다. 하지만 스몰토크 벤더들은 배치된 이미지로 컴파일러를 이용하는 데에 상당한 런타임 비용을 청구하여 대부분 시스템에서는 비용을 정당화시키기가 힘들다. 이러한 이유로 완전한 스몰토크 컴파일러로 얻는 추가적 유연성보다 재정적 고려 사항이 더 많을 경우 Interpreter 패턴을 사용하면 되겠다.

Interpreter 패턴의 핵심은 어떠한 문(statement)이든 단순한 언어로 표현하기 위해 하나의 트리로 구조화할 수 있는 표현식(Expression) 객체의 집합이다. 이러한 Expression들은 서로 다른 타입의 Expression을 위한 서브클래스를 가진 AbstractExpression 계층구조의 인스턴스들이다. 각 Expression은 스스로를 해석하여 결과로 나타내는 값을 리턴할 수 있다. 일부 표현식은 변수와 같을 수도 있다; 그리고 값은 트리가 최근 해석된 Context에 따라 달라진다. 따라서 해석을 요청하는 Client는 이 해석에 Context를 제공해야 하는 경우도 있다.

도메인 아키텍처와 해석자

Interpreter 패턴은 분류하기가 힘든 패턴이다. "해석이 필요한 언어가 존재하거나 추상 구문 트리로 언어에 정의된 문장을 표현하고자 한다면" Interpreter 패턴을 사용할 수 있다 (DP 245). Interpreter 패턴을 적용 시 가장 까다로운 점 중 하나는 당신이 해석할 언어를 가지고 있는지 인식하는 것이다. 개발자가 컴파일러 구성을 정식으로 훈련받지 않은 이상 이 패턴을 적용 시 발생할 많은 문제들에 대해 해결책이 될 수 없음을 발견했다.

반면 특정 문제 도메인을 그 언어로 나타낼 수 있음을 인식한 경우에는 전체 시스템의 아키텍쳐에서 핵심을 형성할 수 있다. 확실히 이는 일반 패턴과 애플리케이션 혹은 도메인 특정적 패턴 사이의 어중간한 영역에 위치한다. 패턴과 그 활용의 이해에 대해 투자해야 할 것이 많아 보일지는 모르나 그로 인한 잠재적 보상은 어마어마하다.

동적 쿼리 언어

애플리케이션은 때때로 데이터베이스에 대한 임의 쿼리를 실행하여 결과를 표시할 기능을 필요로 하기도 하다. 이는 특히 데이터 분석을 실행하는 의사결정 지원 시스템에서 흔히 나타난다. 일부 사례의 경우, 사용자가 단순히 SQL 코드를 쓴 후 해석을 위해 관계형 데이터베이스로 전달하는 것을 허용하는 애플리케이션도 있다. 하지만 객체지향 데이터베이스나 플랫 파일에 데이터가 저장되어 있을 경우 문제가 까다로워진다. 시스템이 심지어 관계형 데이터베이스를 사용한다손 치더라도 raw SQL로 사용자를 노출시키는 일은 권하지 않는다. 이런 일이 발생 시 사용자가 SQL를 알지 못한다거나, 사용자가 보고 변경할 수 있는 데이터를 애플리케이션이 제한하는 경우가 발생할지 모른다.

간단한 쿼리 언어, 사용자가 쿼리를 생성하도록 해주는 편집기, 그리고 이러한 쿼리를 실행하는 해석자에게는 이 패턴이 적격이다. 언어는 쿼리를 단순하고 사용자 친화적인 방식으로 표현한다. 편집기는 사용자가 구문상 올바른 쿼리를 구축하도록 도와준다. 사용자는 해석자가 쿼리에서 문제를 발견하는 기능에 감명을 받기도 한다. 해석자는 쿼리를 실행할 수 있을 뿐 아니라 쿼리를 검증하고 어떠한 보안 사항도 위반하지 않음을 확인해준다.

스크립트와 매크로

런타임 해석자-평가자로 사용이 가능한 또 다른 애플리케이션으로 최종 사용자 스크립팅 언어를 들 수 있다. 많은 상업 생산성 소프트웨어 제품들은 스크립팅 언어를 포함한다. VBA (애플리케이션을 위한 비주얼 베이직)과 이와 비슷한 유형에서는 고객들이 자신이 원하는 방식대로 자신의 소프트웨어 툴을 통제할 수 있을 것으로 기대한다.

패턴의 또 다른 애플리케이션으로 매크로 개발이 있다. 일부 툴은 자주 실행되는 오퍼레이션 단계의 series를 사용자가 정의하고, 핫 키에 series를 할당하며, 버튼을 누르면 series를 실행하도록 해준다. 대부분 워드 프로세서에서 이와 같은 매크로 정의의 기능을 제공한다는 것을 예로 들 수 있다.

매크로는 Interpreter 패턴이나 Command 패턴의 예가 될 수 있다. 사용자가 만약 매크로 편집기를 사용해 텍스트적 언어를 이용해 추상적으로 매크로를 발전시키고자 할 경우, 이는 Interpreter 패턴에 해당한다. 반면 실행작용의 series을 수행하고 이를 기록함으로써 매크로를 생성하는 경우가 종종 있다. 이러한 기법은 Command 기법으로서, 각 실행작용이 Command도 기록되고 전체로서 series는 복합 Command로 (245) 구현된다. 물론 두 기법을 결합할 수도 있으므로 굳이 하나만 사용할 필요는 없다.

스크립팅을 넘어서

Interpreter 패턴은 자신만의 작은 스크립팅 툴을 개발하고자 하는 개발자들에게 안내서를 제공하지만 문제해결로 제한하진 않는다. 위에서 언급하였듯 Interpreter 패턴을 적용 시 곤란한 점들 중 하나는 당신이 해석자를 쓸 수 있는 언어를 갖고 있는지 인지하는 것이다. 문제가 되는 언어를 확인한 후 파스 노드의 클래스 계층구조를 구축하여 언어를 나타내는 것이 Interpreter 패턴의 핵심이다.

[디자인 패턴] 편에서는 Interpreter 패턴이 두 가지 타입의 언어, 정규 표현식과 불 연산식에 적용됨을 보여준다. 이 단순한 언어는 훌륭한 예제가 되긴 하지만 [디자인 패턴]의 많은 독자들은 다른 타입의 언어에 이 패턴을 적용시킨 사례를 보지 못할 것이다. "새로운 시야로 바라보고" 도메인 특정적 언어와 관련된 여러 종류의 문제를 표현할 수 있음을 이해하는 것은 숙련된 설계자들에게 나타나는 특징 중 하나이다.

파서

Interpreter 패턴의 의도 단락에는 두 가지를 구분하여 명시한다:

  1. 문법에 대한 표현을 정의한다.
  2. 그 언어로 기술된 문장을 해석하는 기법을 표현한다.

앞서 언급하였듯, Interpreter 패턴을 사용하는 프로그램은 고수준의 표현식을 기본적 표현으로서 파싱하는 과정을 수반한다. 하지만 파서 자체는 파싱 후 표현식을 해석하거나 평가하지 않기 때문에 Interpreter로 "취급하지 않는다." 이것이 바로 Interpreter 패턴과 관련된 일반적인 오해이다. 그럼에도 불구하고 파서들은 고수준 표현식을 기본적 형태로 나타내기 위해 Interpreter 패턴의 것과 동일한ㅡ또는 완전히 동일한ㅡ클래스와 구조를 사용한다.

물론 다양한 스몰토크 컴파일러들이 스몰토크 표현식을 저수준 트리 구조로 분해하기 위해 파서를 포함하기도 한다. 그 외 많은 스몰토크 시스템들도 Interpreter와 동일한 클래스 구조를 사용하는 파서를 포함한다; 이 중 몇 가지를 여기서 언급하고자 한다. TGen 파서 생성 프로그램은 UIUC Smalltalk Archives에서 무료로 사용할 수 있다.[1] Outback Software의 Dave Collins는 Perl, Lex, 그리고 다른 언어들의 문법으로 쓰인 표현식을 이해하는 RegExp란 이름의 정규 표현식 컴파일러-인식기를 발전시켰다. ParCE(Alpert & Rosson, 1992)는 context-free 파서로서, 자연 언어로 된 문장들을 비단말적 문장성분과 (명사구, 전치사구, 동사구 등) 단말적 문장성분으로 (명사, 동사, 전치사, 한정사 등) 구성된 기본 트리 구조로 분해하기 위한 목적으로 만들어졌다.

예제 코드

임의 쿼리 언어를 어떻게 발전시키는지 간단한 예를 살펴보자. 환자 리스트를 유지하는 건강보험 애플리케이션을 다루고 있다고 가정하자. 우리는 다음과 같은 클래스 정의를 통해 스몰토크에서 환자를 정의한다:

Object subclass: #Patient
	instanceVariableNames: 'name streetAddress city state
		zip sex dateOfBirth policy'
	classVariableNames: ''
	poolDictionaries: ''

애플리케이션은 구체적 값에 일치하는 속성을 가진 환자들을 모두 검색할 필요가 있다. 예를 들어, 클리블랜드에 거주하는 환자들을 모두 찾아야 한다고 가정하자. 환자 리스트를 가지고 있다면 Interpreter가 없이도 스몰토크에서 쉽게 실행할 수 있다. 단순히 Collection에 정의된 select: 를 이용하면 된다 (Iterator (273) 참조).

aCollectionOf Patients select:
	[:each | each city = 'Cleveland']

이보다 조금 더 복잡한 쿼리라 하더라도 간단한 스몰토크 코드를 이용해 해결된다. 예를 들어, 다음 코드를 이용하면 환자 리스트에서 오하이오 주 클리블랜드에 (미시건 주의 클리브랜드나 인디애나 주의 클리브랜드가 아닌) 거주 중인 환자를 모두 검색이 가능하다.

aCollectionOfPatients select:
	[:each |
	(each state = 'OH')
		and: [each city = 'Cleveland']]

이러한 코드는 당신이 코드를 작성하기 전에 필요한 쿼리를 모두 알고 있는 경우에만 효과가 있다. 하지만 사용자가 런타임 시 새로운 쿼리를 명시해야 할 경우는 어떨까? Interpreter 패턴이 이 문제를 해결해준다.

우리가 명시하고자 하는 것은 SQL 문의 WHERE 구(phrase)와 같은 단순한 쿼리 언어이다. 우리는 객체의 속성을 바탕으로 하여 집합체로부터 어떤 객체를 선택할지 명시할 방법이 필요하다. 불 연산으로 (AND나 OR과 같은) 여러 개의 단순 쿼리를 모아 좀 더 복잡한 쿼리를 형성할 수도 있다.

단순 쿼리

단순 쿼리부터 시작해보자. 구체적 값에 일치하는 속성을 가진 객체만 선택하고 싶을 때가 있다. 우리는 객체의 어떠한 속성이든 그 값을 얻을 수 있어야 한다. 이는 객체의 클래스가 문제가 되는 속성에 대한 getter 메드를 구현할 경우 쉽게 해결된다. getter 메소드의 이름을 알고 있다면 perform: 메소드를 사용해 속성의 값을 찾아 메소드를 실행할 수 있다ㅡ예를 들어, aPatient perform: #name은 aPatient의 name 값을 리턴한다. perform: 에 대한 argument는 Symbol이므로 미리부터 속성의 이름을 알 필요가 없다. 필요 시 사용자에게 요청하면 될 일이다.

이는 우리의 쿼리 언어에서 가장 단순한 부분, 즉 동일성 검사 문제로 옮겨간다. 우리는 언어에서 aspectName = someValue 형태의 문장을ㅡ예: city = ‘Cleveland’ㅡ해석하길 원한다고 가정하자. Interpreter 패턴을 이용해 우리는 다음 클래스 계층구조를 구현함으로써 문장을 다음과 같이 처리할 수 있다:

Dpsc chapter05 Interpreter 02.png

클래스 QueryNode는 메시지 evaluateAgainst: 를 정의한다; 모든 구체적 서브클래스는 이 메시지를 오버라이드해야 한다. 당신의 쿼리 언어를 이용해 구성된 문장이나 표현식으로부터 런타임 시 파스 트리를 구성하여 파스 트리로 표현된 쿼리를 평가(또는 해석)하는 것이 기본적인 생각이다. 우리 예제에서 해석 파스는 "이 특정 컨텍스트에 네가 무슨 값을 가지는지 가서 확인해보라"라는 의미를 가진 evaluateAgainst: 메시지를 파스 트리의 루트 노드로 전송함으로써 처리된다. 이를 더 자세히 이해하기 위해 위의 다이어그램에 나타나는 3개의 클래스의 구현을 살펴보고자 한다:

Object subclass: #QueryNode
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	
QueryNode>>evaluateAgainst: anObject
	"Return the result of evaluating myself
	against anObject."
	^self subclassResponsibility

AttributeNode는 메시지 argument인 anObject로 perform: 을 전송한 결과를 리턴시키는 evaluateAgainst: 를 구현한다. 실행될 메시지는 노드의 attributeName 변수에 보관된다. 이것은 가장 단순한 사례에 속한다; 좀 더 복잡한 사례라면 perform:with: 또는 perform:withArguments: 를 구현해 추가 파라미터를 전송하는 과정이 수반될 것이다:

QueryNode subclass: #AttributeNode
	instanceVariableNames: 'attributeName'
	classVariableNames: ''
	poolDictionaries: ''

AttributeNode>>evaluateAgainst: anObject
	"Return the result of sending anObject the message
	selector contained in my attributeName variable"
	^anObject perform: attributeName

객체에 대한 ValueNode를 평가하면 그것이 포함한 상수 값을 리턴할 뿐이다. 또한 그의 value 인스턴스 변수에 대한 접근자 메소드도 구현할 것이다:

QueryNode subclass: #ValueNode
	instanceVariableNames: 'value'
	classVariableNames: ''
	poolDictionaries: ''

ValueNode>>evaluateAgainst: anObject
	"Return my value. I do not care about
	anObject since I'm a constant."
	^value

EqualsNode는 두 개의 자식을 가진다. 이것은 anObject에 대해 평가한 후 그 결과에서 동등성을 비교한다:

EqualsNode>>evaluateAgainst: anObject
	"Answer whether my two children produce equal results
	when evaluated against anObject"
	^(leftChild evaluateAgainst: anObject)
		= (rightChild evaluateAgainst: anObject)

어떻게 작용하는지 살펴보기 위해 위의 예제를 취하여 어떻게 그 문장에 대한 파스 트리를 구성하는지 살펴보자:

| equals value attribute patients |
"Get a list of patients."
patientList := Patient examplePatients.
equals := EqualsNode new.
value := ValueNode new.
attribute := AttributeNode new.
attribute attributeName: #city.
value value: 'Cleveland'.
equals
	leftChild: attribute;
	rightChild: value.
^patientList select:
	[:each | equals evaluateAgainst: each]

다음 다이어그램은 앞의 예제 코드의 구조를 보여준다:

Dpsc chapter05 Interpreter 03.png

위의 코드에서 당신은 미리 쿼리의 구조를 알아야 한다거나 select: 문을 직접 코딩하는 것이 최고의 방법이라고 추측할지도 모른다. 하지만 이는 사실이 아니다. Interpreter 패턴에서 중요한 또 한 가지 개념은, 우선 노드에 대한 클래스를 구성하고 나면 파스 트리의 실제적 구축은 런타임 시간까지 연기될 수 있다는 점이다. 실제로 당신은 사용자가 제공한 쿼리 표현의 일부를 읽어 (예: 쿼리의 텍스트) 그로부터 적절한 파스-트리 노드를 생성할 수 있는 파서를 구축해야 할 것이다. 당신이 사용하는 언어가 충분히 단순하다면 그러한 파서를 하드코딩하는 것이 오히려 간단한 방법이겠다. 단 좀 더 복잡한 언어라면 TGen 파서 생성기와 같은 도구를 이용해야 할지도 모르겠다.

복잡한 쿼리

위의 해법은 기존에 정의된 타입의 단순 쿼리를 대상으로 할 때 훌륭한 해법이다. AND나 OR를 필요로 하는 복잡한 쿼리를 처리해야 한다면 디자인을 리팩토링하여 다음과 같이 확장시킬 필요가 있겠다:

Dpsc chapter05 Interpreter 04.png

이러한 새로운 디자인은 단말 노드와 비단말 노드 사이가 단순히 구분되는데, 각 비단말 노드는 좌측과 우측에 자식을 하나씩 가진 이진 노드이다. 이는 NonterminalQueryNode 클래스를 추가함으로써 이루어진다. 이 클래스의 서브클래스들은 자식 노드를 표현하기 위해 인스턴스 변수 rightChild와 leftChild를 상속한다.

AndNode와 OrNode 클래스는 이전에 EqualsNode에 대해 나타낸 것과 거의 동일하게 evaluateAgainst: 를 구현한다. AndNode 클래스는 두 가지 노드를 모두 평가하며, and:를 이용해 둘을 통합시킨 결과를 리턴한다. OrNode 클래스도 no: 를 이용해 똑같은 작업을 수행한다. 둘 중 어떤 클래스도 추가적 인스턴스 변수를 추가하지 않는다:

AndNode>>evaluateAgainst: anObject
	"Evaluate both of my child nodes against anObject and
	then return the logical AND of these evaluations."
	^(leftChild evaluateAgainst: anObject)
		and: [rightChild evaluateAgainst: anObject]
		
AndNode>>evaluateAgainst: anObject
	"Evaluate both of my child nodes against anObject and
	then return the logical OR of these evaluations."
	^(leftChild evaluateAgainst: anObject)
		and: [rightChild evaluateAgainst: anObject]

이것이 어떻게 작용하는지 살펴보기 위해 불(Boolean) 노드를 사용하는 다음 예제를 고려해보자:

| cityEquals cityValue cityAttribute stateEquals stateValue
stateAttribute andNode |
"Get a list of patients."
patientList := Patient examplePatients.

cityEquals := EqualsNode new.
cityValue := ValueNode new.
cityAttribute := AttributeNode new.
cityAttribute attributeName: #city.
cityValue value: 'Cleveland'.
cityEquals leftChild: cityAttribute;
	rightChild: cityValue.

stateEquals := EqualsNode new.
stateValue := ValueNode new.
stateAttribute := AttributeNode new.
stateAttribute attributeName: #state.
stateValue value: 'OH'
stateEquals leftChild: stateAttribute;
	rightChild: stateValue.

andNode := AndNode new.
andNode leftChild: cityEquals;
	rightChild: stateEquals.
^patientList select: [:each |
	andNode evaluateAgainst: each].

다음 다이어그램은 앞의 예제에 나타난 구조를 보여준다:

Dpsc chapter05 Interpreter 05.png

위의 설계에서 우리가 갖고 있는 유일한 비교 연산자는 equality라는 사실을 알아챘을 것이다. 더 많은 비교 연산자를 추가하는 것은 간단하다. LessThanNode, GreaterThanNode, NotEqualsNode는 구현이 쉽고 EqualsNode와 유사하다. 형식의 충돌을 확실히 막기 위해 필요 이상이나 이하의 형식 검사를 도입한건 아닌지 주목하라. [디자인 패턴] 편의 (DP 331) Visitor 패턴은 이에 관한 정보를 제공한다.

이 쿼리 언어는 select: 문에만 사용되도록 제한되지 않는다. LessThanNode와 GreaterThanNode 클래스가 추가되었을 경우 그것은 sort block을 형성하는데 사용될 수도 있다. 이 예제를 확장한 경우가 바로 그때그때 봐 가며 표현식을 구성하는 비주얼 빌더가 된다. 문법에 있는 표현식을 적절한 노드로 파싱하는 방법도 하나의 대안법이 된다.

알려진 스몰토크 사용예

팩토리 스케줄링 시스템

Interpreter 패턴을 적용 시 겪는 어려움들 중 하나는, 도메인 특정적 언어로 특정 문제를 표현할 수 있는지를 인식하는 것과 관련된다. 우리는 이용 가능한 자원의 자원 스케줄링을 위해 팩토리 스케줄링 시스템에 Interpreter 패턴을 사용해왔다. 기본 문제는 이렇다. 스케줄을 구축하고자 하는 자원 타입이 3가지가 있다: 노동자, 기계, 도구. 특정 유형의 업무를 실행하려면, 특정 노동자 (숙련된 조작자 모임으로부터 도출한), 기계 (동일한 기계 모음으로부터 도출한), 도구의 (동일한 도구 모음으로부터 도출한) 결합을 동시에 이용할 수 있어야한다.

예를 들어, 위젯을 만들려면 드릴 프레스 (팩토리에 2가지 종류가 있음), 10-mm 드릴 비트 (팩토리에 3가지 종류가 있음), 드릴 프레스 조작자가 (팩토리에 자격을 가진 사람이 2명 있음) 필요하다. 우리 시스템에 각 객체는 24시간 슬롯으로 구성된 달력을 가지고 있다고 가정하자. 각 슬롯은 채워져 있거나 (예약) 비어 있다. 이용할 수 있는 노동자, 기계, 도구의 다양한 조합을 평가하는 것은 업무의 스케줄링의 일부이다. 조합의 평가는 규칙 집합이 하는 역할임을 알고 있었지만 이러한 규칙들이 평가할 검색 공간을 표현할 필요가 있었다. 이를 위해 다음과 같은 계층구조를 이용하였다:

Dpsc chapter05 Interpreter 06.png

시스템의 각 객체는 (노동, 기계, 또는 도구) SimpleCalendar의 인스턴스를 인스턴스 변수들 중 하나로서 갖고 있다. 스케줄링은 일주일을 경계로 발생하므로 SimpleCalendar은 일주일에 포함된 일일을 표현하는 배열을 포함한다. 일일에 대한 배열 인수는 Reservation의 인스턴스를 포함하거나ㅡ당일 예약된 그 객체를 나타내는ㅡ비어 있다.

이제 스케줄링 문제를 표현하는 데에 달력과 관련해 문제가 발생한다. 우리 사례에서 코드는 다음과 비슷할 것이다:

availabilityCalendar :=
	((drillPressOne calendar or: drillPressTwo calendar)
		and: ((bitOne calendar or: bitTwo Calendar)
			or: bitThree calendar)
				and: (bob calendar
					or: steve calendar)) availabilityCalendar

이 메소드는 사실상 노드로 이루어진 파스 트리를 구성한다. 가용성 달력에게 스스로를 평가하도록 요청하면 재미있는 부분이 발생한다. 이번 경우, 컴포넌트 부분에서 가용성 달력을 구성함을 의미한다. ANDedCalendar와 ORedCalendar는 이 구성에서 서로 다른 작업을 수행한다. ORedCalendar는 좌측과 우측 달력에서 자원에 대한 concatenation을 리턴할 것이다. 반대로 ANDedCalandar는 좌측과 우측 달력의 결과를, 오른쪽에 가능한 시간 슬롯을 왼쪽의 것과 순열(permutation)시킨 결과에 결합한다. 따라서 최상위 단계에는 ResourceScheduler가 노동자, 기계, 도구의 모든 가능한 조합의 집합을 수신하며, 그것의 규칙들은 "최고의" 조합을 평가할 수 있다.

이 해법의 구조는 불필요한 경로를 제거함으로써 불필요한 공간을 쳐내기 쉽게 만든다. 예를 들어, 우리 예제에서 하나의 도구가 이미 일주일 내내 예약되어 있다면 그 달력을 포함하는 ORedCalendar는 이러한 사실을 감지하고 그것을 concatenation에 포함되는 것을 거부할 수 있다.

규칙 엔진으로서의 스몰토크

Davidowitz (1996)는, 비지니스 객체로부터 비지니스 원칙을 외부화시키기 위한 규칙 언어를 구축하는 데에 있어 스몰토크 해석자를 어떻게 사용하는지를 보여준다. Davidowitz는 또한 비주얼웍스 스몰토크의 컴파일러가 제공하는 프로그램 노드를 확장시키는 데에 Decorator 패턴의 사용을 언급하기도 한다.

비주얼웍스 UIBuilder

비주얼웍스의 UI Builder 시스템에서도 Interpreter의 특이한 사용예를 찾을 수 있다. Yelland (1996)는 UI Builder가 Interpreter패턴을 사용하여 추상 사양으로부터 사용자 인터페이스를 인스턴스화하는 3단계 과정을 가진다고 주장한다. 이 단계는 다음과 같다:

  1. UIBuilder는 windowSpec 메소드로부터 리터럴 문자 배열(literal array of strings)을 읽어온다.
  2. 리터럴 배열은 파스 노드의 트리를 구성하는 데에 사용된다 (UISpecification 서브클래스의 인스턴스).
  3. UISpecification 노드는 LookPolicy와 UIBuilder와 함께 그들의 표현을 구축할 것을 요청받는다.

이것이 실제로 Interpreter 패턴의 사용인지에 대해서는 아직도 토론이 필요하다. 단, 한 가지 빠뜨린 핵심 요소는 파싱된 실제 언어가 없다는 것이다. 대신 UI Builder에서 구축된 비주얼웍스 윈도우 창의 복합 구조가 이후 재구성 목적을 위해 WindowSpec 메소드에 Strings의 배열로 저장된다.

여기서는 UISpecifications에게 그의 표현들을 구축도록 요청하는 방식이 Interprefer 또는 Composite 패턴을 결정짓는가라는 질문이 제기된다. 둘 중 어떤 패턴을 사용해도 괜찮다. 당신이 배열을 "언어" 부류로 간주한다면, 이는 Interpreter 패턴의 정의와 일치한다. 반면 언어라고 생각되지 않는다면 Interpreter 패턴의 정의와 맞지 않으며, 대신 Composite 패턴의 사용으로 간주해야 한다.

Notes

  1. UIUC (일리노이대학교 어바나-샴페인 캠퍼스) Smalltalk archives는 Ralph Johnson과 그의 대학원생들이 관리하며, 여러 방언들에 대한 스몰토크 코드를 무료로 제공한다. http://st-www.cs.uiuc.edu/ 를 통해 이용할 수 있다.