DesignPatternSmalltalkCompanion:Observer

From 흡혈양파의 번역工房
Revision as of 10:48, 1 August 2012 by Onionmixer (talk | contribs) (DPSC OBSERVER 페이지 추가)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

OBSERVER (DP 293)

의도

객체 사이에 일 대 다의 종속성을 정의하고 한 객체의 상태가 변하면 종속된 다른 객체에 통보가 가고 자동으로 수정이 일어나게 한다.

구조

Dpsc chapter05 Observer 01.png

다음 페이지에 나타난 상호작용 다이어그램은 주제대상(subject)과 두 관찰자(observer) 간의 협력관계를 설명한다:

Dpsc chapter05 Observer 02.png

논의

객체지향 기법은 설계자가 문제를 작은 규모의 책임 집합을 가지지만 복잡한 업무를 수행할 때는 협력하는 조각들로ㅡ객체들ㅡ분해할 것을 장려한다. 이로 인해 각 객체는 구현 및 관리가 쉽고, 재사용이 가능해지며, 조합을 더 유연하게 만든다. 단점이라고 하면, 어떠한 단순한 행위라도 다수의 객체에 걸쳐 분산되어 한 객체에 일어난 변화가 다른 많은 객체에 영향을 미친다는 점이다. 모든 변화가 모든 관련 객체에게 영향을 미치도록 구현할 수도 있지만 이럴 경우 모든 객체들이 서로 묶여 있어 유연성을 망칠 것이기 때문에 객체들은 변화와 이벤트에 관해 서로 이야기할 수 있는 쉽고 유연한 방법이 필요하다.

Observer 패턴의 핵심은 두 개의 객체 중 하나의 객체가 나머지 객체가 공유해야 하는 상태를 보유한다는 점이다. 이를 주제대상(Subject)과 관찰자(Observer)라고 부르며, 주제대상은 상태를 보관하고 관찰자는 그것을 공유한다. 주제대상과 그 관찰자의 협력은 매우 간접적이므로 관찰자는 선택적(optional)이다. 이런 방식을 통해 주제대상은 관찰자가 하나 또는 전혀 없을 때만큼이나 수월하게 다수의 관찰자를 지원할 수 있다. 대상은 자신이 관찰자를 갖고 있는지 알지 못하기 때문에 만약의 경우를 대비하여 자신의 상태에 대한 변경을 항상 알려준다. 관찰자가 되고자 하는 각 객체는 대상에게 신청을 하고 변경 소식을 수신한다. 각 관찰자는 임무를 수행하는 동시 대상으로부터 상태 변화에 대한 통보를 기다린다. 관찰자라고 부르는 이유는 대상의 상태변경을 관찰하는 방식과 자신의 주제대상과 동시에 움직이도록 유지하는 방식 때문이다. 관찰자는 상태 변경의 통지를 수신하면 자신만의 방식으로 반응하며, 무관한 변경 내용은 무시할 수도 있다. 그 동안 주제대상은 관찰자가 있는지 혹은 얼마나 많은 관찰자가 특정 변경 내용에 반응하는지 전혀 알지 못한다. 이러한 관찰 관계는 주제대상으로 하여금 자신의 행위에 집중하고 동기화 책임을 관찰자들에게 분배할 수 있도록 허용한다.

스몰토크에서 사용되는 Observer 패턴에서 한 가지 혼란스러운 점은 Subject와 Observer 프로토콜이 하나의 클래스, Object에서 구현된다는 점이다. 즉, 어떤 객체든 Subject 또는 Observer, 또는 두 개의 역할을 한꺼번에 할 수 있다. 따라서 패턴이 Subject나 Observer를 언급하면 그것의 클래스가 아니라 객체의 역할 차이임을 명심해야 한다. 객체는 Subject나 Observer의 인스턴스가 아니다; 단지 하나의 객체일 뿐이다.

패턴인가 아니면 언어의 특성인가?

스몰토크-80와 같은 초기 스몰토크에는 객체들 간 3가지 유형의 관계가 있었다 (Goldberg & Robson, 1983, p.240):

  1. 다른 객체들에 대한 (예: 인스턴스 변수들) 객체의 참조.
  2. 클래스와 그 슈퍼클래스 및 메타클래스 간의 관계 (예: 클래스의 인스턴스 side는 그것의 클래스 side와 슈퍼클래스에 대해 알고 있다).
  3. 객체가 자신을 다른 객체에 대한 종속자(dependent)로 등록하는 기능.

이 중 “서로 다른 객체들 간 활동을 조정하기 위해” 사용되는 3번째 유형, 즉 의존성 관계가 Observer 패턴에 속한다. 이는 계층구조 내 서로 다른 계층들 간 소통에서 사용된다 (Brown, 1995a). 가장 잘 알려진 사용예는 ParcPlace Smalltalk에서 사용자 인터페이스의 구현에 사용하는 MVC 프레임워크이다. MVC는 인터페이스를 두 개의 객체 계층으로 분해한다: 모델 계층과 뷰-컨트롤러 쌍 계층. MVC의 지도 원리는 모델이 뷰-컨트롤러 쌍의 수가 0이든 다수이든 원하는 수만큼 지원해야 한다는 것이다. 따라서 모델은 뷰-컨트롤러와 직접 협력하지 않는다. 종속자ㅡ변경 내용을 통지받고 싶다고 등록한 객체들ㅡ에게 변경 내용을 알려줄 뿐이다. 각 뷰와 컨트롤러는 그것이 표시 중인 모델에 대한 종속자로서 자신을 등록한다. 모델이 변하면 뷰와 컨트롤러가 통지를 받는다. 스몰토크의 MVC는 그 외 설계된 다른 모든 그래픽 윈도우 시스템들의 표준이 되었다.

Observer 패턴이 스몰토크에서 기본적인 것으로 보이긴 하지만 언어적 특징은 아니다; 대부분 스몰토크 라이브러리의 특징이다. 모든 방언은 이 패턴을 서로 다르게 구현한다. 제안된 ANSI 스몰토크 표준에서는 Observer 패턴을 전혀 포함하지 않는다 (X3J20, 1996). 게다가 윈도우 페인터를 비롯해 다른 비주얼 프로그래밍 도구들이 Observer 패턴의 사용을 자동화시켜 프로그래머들이 직접 사용하지 않아도 되게끔 만들었다. 그럼에도 불구하고 Observer는 스몰토크 프로그래머들이 이해해야 하는 유의한 특징이다.

스몰토크-80에서의 Observer 패턴

Observer 패턴은 변경 / 업데이트 프로토콜에 의해 구현되며, 스몰토크-80에 도입되어 비주얼웍스에는 오늘날에도 여전히 사용된다 (Woolf, 1994). 비주얼 스몰토크나 IBM 스몰토크에서도 비슷한 형태의 프로토콜이 존재하지만 이 방언들은 대부분 이후에 설명된 이벤트 프로토콜을 사용한다.

스몰토크-80의 변경 / 업데이트 프로코콜은 Object에서 Subject와 Observer 클래스의 결합으로서 구현된다. 이는 어떤 객체든 Subject, Observer (스몰토크는 이를 종속자라고 부름), 또는 둘 다 될 수 있다. Subject가 변경되면 스스로 변경 메시지 중 하나를 전송한다. 프로토콜은 이 메시지를 이에 상응하는 업데이트 메시지로 변환하고, 이것은 Subject의 각 종속자에게 전송된다. 이 메시지들은 다음과 같다:

Dpsc chapter05 Observer 03.png

변경 / 업데이트 프로토콜은 Observer 패턴에 키 확장(key extension)을 추가한다. 주제대상은 자신이 변경되었음을 알리면서 무엇이 변경되었는지도 명시한다. 변경 내용을 알릴 때에는 대상이 변경된 업데이트 aspect도 명시한다. 업데이트 aspect은 주로 Symbol 객체로서, 대상에 발생할 수 있는 변화 유형을 명시한다. 주제는 자신의 변경 내용에 대한 업데이트 aspect을 정의하고, 종속자들은 업데이트 aspect을 확인함으로써 변경 내용들 간 차이를 구별한다. 업데이트 aspect가 종속자의 관심대상과 다를 경우 종속자는 변경 알림을 무시할 것이다.

주제대상은 변경 메시지 중 하나를 스스로 전송함으로써 변경을 알린다:

  • changedㅡ주제대상이 무엇이 변했는지는 명시하지 않은 채 변경 여부만 알릴 때 changed를 전송한다.
  • changed: ㅡ주제대상이 구체적 aspect가 변경되었음을 알릴 때 changed: 를 전송한다. anAspectSymbol 파라미터는 일반적으로 Symbol로서 변경된 aspect의 업데이트 aspect를 명시한다.
  • changed:with: ㅡ주제대상이 구체적으로 어떤 aspect가 변경되었는지 뿐 아니라 변경 내용에 관한 추가 정보도 함께 알릴 때changed:with: 를 전송한다. 두 번째 파라미터 aParameter는 추가 정보로서 어떠한 Object든 이것이 될 수 있다. 때로는 변경된 aspect에 대한 새 값이 되기도 한다.

종속자는 일련의 업데이트 메시지를 수신함으로써 변경 통지를 수신한다. 기본적으로 종속자는 모든 업데이트를 무시할 것이다. 변경 통지를 모두 듣길 원하는 서브클래스는 하나 또는 그 이상의 업데이트 메시지를 구현해야만 한다:

  • update: ㅡ종속자가 어떤 업데이트 aspect가 변경되었는지 알아야 하는 경우 이 메시지를 구현한다. anAspectSymbol 파라미터는 대상이 변경 알림을 생성할 때 대상이 명시하는 업데이트 aspect이다. 주제대상이 업데이트 aspect를 구체화하지 않았다면 파라미터는 nil이 된다.
  • update:with: ㅡ주제대상이 변경 내용에 대해 명시한 추가 정보를 종속자가 알아야 하는 경우 종속자는 이 메시지를 구현한다. 두 번째 파라미터 aParameter는 changed:with: 에서 두 번째 파라미터와 동일한 객체이다.
  • update:with:from: ㅡ어떤 주제대상이 변경을 알렸는지 종속자가 알아야 하는 경우 종속자는 이 메시지를 구현한다. 세 번째 파라미터 aSender는 변경 알림을 전송한 주제대상이다.

스몰토크-80: 의존성 생성

주제대상은 이미지에 있는 다른 모든 객체에게 자신의 변경 내용을 알리진 않는다. 이러한 행동은 비효율적이다. 대신 특정 알림을 수신하고자 하는 객체는 원하는 알림을 제공하는 하나의 객체 또는 객체들로서 자신을 등록한다. 이런 방식을 통해 변경에 대해 알고 싶다고 말한 객체들에게만 정보를 알려준다. 객체가 다른 객체로부터 알림을 수신하고자 하는 경우, 아래와 같이 addDependent: 와 같은 메시지를 이용해 그 객체에 대한 종속자로서 자신을 등록한다. 알림을 더 이상 받고 싶지 않을 때는 다음과 같이 removeDependent: 메시지를 사용하여 종속자로서 스스로를 제거한다:

aSubject addDependent: aDependent.
...
aSubject removeDependent: aDependent.

주제대상은 집합체를 사용하여 종속자들을 추적한다. Object에서 이러한 집합체는 DependentsFields 사전에 있는 값이다. 종속자가 있는 객체들만 이 사전에 키를 갖는다. Model은 DependetsFields를 우회하는 고유의dependents 인스턴스 변수로 종속자를 더 효율적으로 추적한다. 따라서 변경 알림을 발표할 객체는 Model의 서브클래스일 경우 더 효율적이겠다.

addDependent: 와 removeDependent 메시지는 주로 각각 종속자의 initialize와 release의 구현부로부터 전송된다. 종속자는 동일한 대상에 대한 종속자로서 스스로를 여러 번 등록할 수 있으며 중복된 종속성을 생성한다. 그러나 종속성은 일반적으로 동일한 대상에 대해 하나 이상의 종속성을 가진다는 사실을 인식하지 못하기 때문에 비효율적인데다가 종속성을 끊기가 힘들다. 따라서 종속자는 대상에 대한 종속자로서 한 번만 등록할 것을 주의한다. 그와 같이 removeDependent: 의 단일 전송은 두 객체들 간 유일한 종속성을 깰 것이다.

때때로 의존 객체들은 쓰레기로 적절하게 수집되지 못하는 것처럼 보인다. 시간이 지나면서 이는 확실한 이유 없이 개발 이미지가 점점 더 커지고 느려지는 현상을 야기할 것이다. 더 이상 사용하지 않는 객체들을 왜 쓰레기로 수집되지 않을까? 이유는 그들의 의존성이 아직 구축되어 있고 그들의 주제대상이 아직 쓰레기로 수집되지 않았기 때문이다. 대상이 쓰레기로 수집되기 전까지는 종속자도 수집될 수 없다. Model에 속하지 않는 객체에 대한 (예: DependentsFieilds 또는 Dependents) 종속자를 추적하는 데에 사용되는 Object 내의 클래스 변수도 주제대상이 더 이상 사용되지 않을 때 비슷한 문제를 야기한다. 따라서 종속 객체가 더 이상 사용되지 않을 때 그것은 종속성을 해제하여 쓰레기로 수집될 수 있도록 해야 한다.

비주얼웍스에서 Observer 패턴의 사용

스몰토크-80는 비주얼웍스의 기반이 되므로 비주얼웍스는 위에 설명한 변경 / 업데이트 프로토콜을 포함한다. 비주얼웍스가 윈도우 페인터를 소개할 당시 Observer 패턴의 구현에 새로운 특징 몇 가지를 추가하였다.

비주얼웍스: DependencyTransformer

변경 / 업데이트로 인해 객체들 간 종속성 관계를 따르기가 힘들 때가 종종 있다. 프레임워크는 수많은 변경 알림을 발표하는 몇 개의 주제대상과 수많은 변경 알림을 듣는 몇 개의 종속자를 포함할 수 있다. 한 가지 변경사항과 그에 상응하는 응답 간의 원인 및 효과는 특히 정적 코드에서 관찰하기가 어렵다. 따르기가 까다로운 제어의 흐름(threads of control)은 잘 유지할 수가 없다. 따라서 변경 / 업데이트는 주제대상 내 변경과 그에 상응하는 종속자 내의 반응 간 관계를 더 잘 구현할 방법이 필요하다.

비주얼웍스는 DependencyTransformer라 불리는 클래스로 이 관계를 규명한다. DependencyTransformer는 대상의 일반 update: 메시지를 종속자의 인터페이스에서 좀 더 구체적인 메시지로 변환하는 종속자를 위한 Adapter (105)이다. 이것은 대상에 일어난 변화와 그에 대한 종속자의 응답을 이어주는 다리 역할을 한다. 그리고 3가지 항목, 즉 종속자, 종속자가 관심을 가진 aspect, 그리고 이 aspect가 변경 시 호출해야 할 메시지를 모두 캡슐화한다. 이는 아래에서 논할 Self-Addressed, Stamped Envelope (우표를 붙인 자기앞 반신용 봉투; SASE) 패턴의 예제이다.

어떤 객체도 주제대상이 될 수 있으므로 어떤 객체든 DependencyTransformer를 가질 수 있다. 아래에서 나타내듯 DependencyTransformer를 생성하는 메시지는 expressInterestIn:for:sendBack: 이며 의존성을 깨는 메시지는 retractInterestIn:for: 이다. 두 개의 메시지는 DependencyTransformer를 대상의 종속자로 만들기 위해 addDependent: 와 removeDependent: 를 사용한다:

aSubject
	expressInterestIn: anAspectSymbol
	for: aDependent
	sendBack: aSelector.
...
aSubject
	retractInterestIn: anAspectSymbol
	for: aDependent.

addDependent: 나 removeDependent: 와 마찬가지로 종속자는 initialize와 release로부터 DependencyTransformer 메시지를 전송하곤 한다.

아래 코드와 다이어그램은 DependencyTransformer가 어떻게 생성되는지, 이것이 어떻게 주제대상과 관찰자 사이에 위치하는지, 그리고 DependencyTransformer 가 업데이트를 처리하여 관찰자에게 알릴 때 발생하는 메시지 상호작용을 보여준다:

ConcreteObserver>>initialize
	...
	aConcreteSubject
		expressInterestIn: #name
		for: self
		sendBack: #nameChanged.
	...

Dpsc chapter05 Observer 04.png

비주얼웍스: ValueModel

변경 / 업데이트의 또 다른 결점은 주제대상의 모든 종속자가 그 대상에 일어나는 모든 변경을 통지받는다는 점이다. 변경은 주로 aspect에 대한 값에 일어나는 변경으로서, 인스턴스 변수의 값이 변경되었음을 의미한다. 주제대상은 종속자가 관심을 가지는 aspect 이상을 포함하는 것이 보통이다. 따라서 종속자는 관심이 없는 aspect에 일어난 변경의 통보를 수신하는 데에 시간을 소요한다. 이는 종속자가 어떤 aspect가 변경되었는지를 잘못 해석할 때 미묘한 버그를 유발할 수도 있다.

비주얼웍스는 ValueModel을 사용하여 aspect와 그 값을 첫 클래스 객체로서 캡슐화한다. 어떤 언어에서는 이것을 “능동 변수(active variable)”라고 부른다. 일반 변수와 달리 ValueModel은 그 자체가 검사, 메시지 수신 등이 가능한 객체이다. 이러한 캡슐화를 구현 시 한 가지 장점은 종속자가 컨테이너 모델과 그의 aspect를 모두 등록하는 대신 하나의 aspect에 대한 종속성만 등록할 수 있다는 점이다. 이러한 방식을 통해 종속자는 ValueModel만을 이용해 자신이 관심을 가지는 aspect들에 대해 등록할 수 있다.

종속자는 ValueModel을 직접 사용하여 등록하지 않는다. 대신 DependencyTransformer를 사용한다. 이는 아래와 같이 onChangeSend : to : 를 이용해 ValueModel에 대한 의존성을 생성하고 해제할 때는 retractInterestsFor : 를 이용한다. 두 개의 메시지는 각각 ValueModel 에 DependencyTransformer 를 생성하고 제거한다:

aValueModel onChangeSend: aSelector to: aDependent.
...
aValueModel retractInterestsFor: aDependent.

다른 종속성 메시지를 사용할 때와 마찬가지로 이 또한 initialize에 구축되어 release에서 제거되는 것이 보통이다.

비주얼웍스: 이벤트

비주얼웍스의 Event 계층구조는 Observer 패턴의 예제로, 업데이트 aspect가 첫 클래스 객체로서 캡슐화된다. 비주얼웍스에 사용되는 컨트롤러는 입력을 위해 한 번 폴링되면 가상머신에게 그에 해당하는 입력이 있는지 반복적으로 요청한다. 현재 컨트롤러는 이벤트 구동으로 이루어진다. 윈도우의 InputSensor는 입력을 Event으로 포착하여 윈도우의 Controller로 전달한다. 따라서 Observer 패턴에서는 InputSensor가 주제대상이고 Controller가 관찰자이며 Event가 어떤 변경이 발생하였는지 (예: 입력) 명시한다.

Observer 패턴의 SASE 변형

[디자인 패턴] 편에서는 Observer 패턴 (DP 296)의 수많은 결과를 열거하는데, 그 중 하나는 예상치 못한 업데이트 문제를 다룬다. 주제대상이 변경 통보를 할 때마다 이러한 변경사항에 관심이 없는 종속자가 있음에도 불구하고 모든 종속자에게 update를 전송한다. 대상이 여러 aspect에 대한 변경을 통지하는데 종속자의 수가 많은 경우, 각 변경 내용이 스스로 통지를 무시하는 몇몇 종속자에게 통지를 한다. 대상이 만일 그 변경 내용에 관심이 있는 종속자에게만 통지를 한다면 훨씬 더 효율적이겠다.

[디자인 패턴] 편에서도 각 관찰자는 고유의 update 메소드를 구현해야만 한다고 설명한다. 이는 관찰자가 추가 프로토콜을 도입하도록 강요하고, 대상과 관찰자들 간 인터페이스를 제한한다. 주제대상은 update 메시지가 있는 선택적 파라미터를 관찰자에게 제공하지 못한다. 게다가 대상은 각 관찰자에게 다른 메시지를 전송할 수 없다; 모든 관찰자에게 동일한 update 메시지를 전송해야 한다. 관찰자가 변경 통지에 응답하는 특정 메소드를 갖고 있다 하더라도 자신이 다른 메시지를 전송하기 위해선 update 메소드를 구현해야만 한다.

이러한 교과서적 Observer 패턴의 단점을 찾기 위해 스몰토크 벤더들은 Observer 패턴의 변형체인 해법들을 개발시켰다. 이 해법은 SASE(Self-Addressed Stamped Envelope) 패턴이라 부른다 (Brown, 1995b). 주제대상이 어떤 메시지를 전송해야 하는지, 대상이 어떤 관찰자에게 메시지를 전송하는지, 언제 메시지를 전송하는지에 대해 관찰자가 명시하기 때문에 전형적인 우편 시스템과 같이 작용하는 SASE 버전이다.

SASE는 Observer 와 Command (245) 패턴을 조합한 것으로서, 특정 이벤트가 발생하면 대상이 어떤 명령어를 보내는지를 관찰자가 말해주는 구조이다. SASE의 작용을 이해하기 위해서는 다음 단계로 구성된 Observer 패턴을 기억한다:

  1. 객체는 주제대상에 대한 관찰자로서 스스로를 등록할 수 있다.
  2. 후에 대상의 상태가 변경 시 스스로 changed: 를 전송한다.
  3. changed: 메소드는 그 대상에 등록된 모든 관찰자에게 update 를 전송한다.
  4. 관찰자가 update를 수신하면 특정 update가 자신에게 적용되는지를 결정하는데, 이는 주로 메시지와 함께 전송되는 aspect 파라미터의 검사로 결정된다.

패턴의 SASE 변형체는 약간 다른 접근법을 취한다:

  1. 관찰자가 주제대상에게 다음 4가지 항목을 제공함으로써 대상에게 자신을 등록한다.
    • 수신자ㅡ관찰자 자신.
    • 이벤트ㅡ관찰자가 관심을 가진 변경사항을 가진 주제대상 내의 aspect
    • 메시지ㅡaspect가 변경 시 관찰자에게 주제대상이 전송해야 하는 메시지의 선택기
    • 세부 사항ㅡ주제대상이 메시지와 함께 전송해야 하는 선택적 파라미터 주제대상은 Letter 객체를 생성하는데 이는 SASE와 같이 사용한다. Letter는 Command 패턴의 명령어와 같아서 그의 수신자, 메시지, 세부 사항을 알고 있다. 대상은 Letter와 그의 이벤트를 연관시킨다. 그 결과 대상은 각 aspect마다 Letter의 목록을 가지며 aspect가 변경 시 이를 발송(실행)한다.
  2. 이후 대상의 상태에 대한 aspect가 변경 시 스스로 triggerEvent: 를 전송한다.
  3. triggerEvent: 메소드는 그aspect에 대한 모든 Letter를 수집하여 발송한다.
  4. Letter를 발송하는 것은 (Command를 실행하는 것) Letter의 메시지를 (스몰토크 메시지 전송) 적절한 파라미터와 함께 수신자에게 전송하는 과정을 수반한다.

이러한 변형체는 Observer와 Command 패턴의 조합이다. Letter는 Command가 되며, Subject는 Invoker에, Observer는 Receiver에 해당된다.

Observer 패턴의 SASE 변형체는 교과서적 Observer 패턴 구현에 비해 몇 가지 장점을 제공한다:

  • 비논리적 업데이트가 없음. SASE를 이용 시 관찰자들은 자신이 관심을 가진 변경에 대한 aspect만 통지를 받는다; 따라서 불필요한 업데이트가 발생하지 않는다. 이러한 사실만으로도 불필요한 메시지에 시간 소모가 없으므로 애플리케이션에 효율성을 크게 증가시킨다.
  • 업데이트 구현부가 없음. 관찰자는 update를 구현하도록 요구되지 않는다. 대상이 통지를 제공하는데 사용할 파라미터와 선택기를 모두 명시할 수 있다. 추가 메시지가 불필요하다.
  • 간단한 관찰자 코드. 관찰자는 통지를 검사하거나 어떻게 응답할 것인지 결정할 필요가 없다. 왜 호출되는지 컨텍스트를 통해 알게 되므로 관찰자 코드를 훨씬 간단하게 구현한다.

위에서 설명한 바와 같이 SASE 패턴은 비주얼웍스에서 DependencyTransformer 클래스로서 구현된다. 비주얼 스몰토크와 IBM 스몰토크는 SASE를 비슷하게 구현한다. 비주얼 스몰토크에서 구현의 이해가 더 쉬운 것은 메시지 이름이 더 쉽기 때문이다.

비주얼 스몰토크에서 SASE 패턴의 사용

비주얼 스몰토크는 SASE 패턴을 이용하여 위젯 이벤트를 윈도우 애플리케이션 객체 내 해당하는 메소드와 연결한다. (위젯이라 함은 텍스트 패인, 드롭다운 리스트, 라디오 버튼, 푸시 버튼 등과 같은 대화식 제어기를 의미한다.) 애플리케이션이 새 윈도우 창을 구성하면 (열기 전에) 애플리케이션 객체는 각 위젯에게 구체적 이벤트가 발생 시 무엇을 할지를 알려준다. 이러한 위젯들은 SubPane의 서브클래스에 의해 표현되는데, 본문에서는 pane이라 부르겠다. 각 pane 클래스는 그의 인스턴스와 관련된 수많은 이벤트를 정의한다. 예를 들어, Button은 사용자가 왼쪽 마우스 버튼으로 구체적 이벤트를 클릭하면 이 이벤트를 생성시키고, 오른쪽 버튼을 클릭하면 다른 이벤트를, 마우스를 버튼 위에서 이동시키면 또 다른 이벤트를 생성한다. 애플리케이션은 이벤트와 애플리케이션 객체를 연결하기 위해 SASE를 사용한다.

예를 하나 살펴보자. 다음 메소드는 가상의 텍스트 편집기 애플리케이션에 위치하며 윈도우의 pane을 생성하기 위한 일반적인 비주얼 스몰토크 코드를 나타낸다:

TextEditor>>open
	| pane|
	...
	pane := Button new.
	pane
		contents: 'Paste';
		when: #clicked
			send: #pasteButtonClicked:
			to: self
			with: pane;
		when: #mouseMoved:
			send: #mouseIsAt:
			to: self;
		...
	self addSubpane: pane.
	...

위의 코드는 “Paste”라는 라벨의 Button을 생성한다. 그러나 현재 논의에서 중요한 문은 when : send : to : with : 와 when : send : to : 메시지이다. 이 메시지들은 텍스트 편집기를 버튼에 대한 2개의 aspect에 관찰자로서 설정한다. 그들은 버튼에게 #clicked 와 #mouseMove: 이벤트가 발생하면 TextEditor에게 통지할 것을 알려주고, 관찰자 TextEditor에게 정확히 어떻게 통지할 것인지를 말해준다.

모든 객체는 그와 관련된 이벤트 실행 테이블을 가질 수 있다. 종속자들에 대한 비주얼웍스의 구현에서와 마찬가지로 이벤트 실행 테이블에 대한 다수의 구현이 있다. 일반적으로 테이블은 IdentityDictionary인데, 이것은 이벤트를 키로 하며 이벤트가 발생 시 수행할 실행 리스트를 값으로 가진다. 종종 이 리스트에는 하나의 관찰자에 대한 하나의 실행만을 포함하지만 다수의 관찰자를 각 이벤트에 연관시킬 수도 있다. Pane은 일부 다른 클래스들과 같이 이 테이블을 handlers라는 이름의 인스턴스 변수에 보관한다; 이는 EventHandlers 라는 이름의 클래스 변수를 사용하는 Object 에서 기본 구현을 오버라이드한다. EventHandlers 는 테이블들에 대한 테이블로서, 주제대상 자체를 키로 가지는 이벤트 테이블의 Dictionary이다.

when : send : to : … 메시지들은 이벤트 실행 테이블에 엔트리를 설정한다. when : argument는 이벤트 이름을 명시한다; send : to : with : 파라미터는 선택기, 수신자, 그리고 함께 Letter 객체를 (실행) 형성하는 argument를 명시한다. 일부 이벤트의 경우ㅡmouseMoved : 와 같은 키워드 메시지로 표현되는ㅡ구체적인 argument를 제공하기보다는 암시되어 있다. 이는 대상에 대해 관찰자 스스로 등록하는 when : 을 사용하여 이루어진다.

아래 테이블은 위의 코드를 실행한 후 Button의 handlers 설정을 보여준다.

Key Value
#clicked a Message (selector = #pasteButtonClicked:)
#mouseMoved: a Message (selector = #mouseIsAt:)

두 가지 Message 객체 모두 수신자는 Button을 생성시킨 TextEditor이다. pasteButtonClicked : 메시지의 경우 argument는 하나의 요소를 가진 Array로서 Button 자체이다. 선택기 mouseIsAt : 를 가진 Message 객체는 (아직) argument가 없다; 이벤트가 실제로 발생 시 제공된다.[1]

이벤트가 발생하면 pane는 그것의 handlers 테이블을 검색하여 적절한 메시지를 전송한다. 마우스 이동 사례를 먼저 살펴보자. 일반적으로 윈도우나 OS/w와 같은 플랫폼에서는 사용자가 pane과 상호작용하면 운영시스템이 pane을 “소유하는” 애플리케이션으로 메시지를 전송한다. 예를 들어, 윈도우 95에서는 사용자가 pane 위에 마우스를 이동시키면 윈도우가 스몰토크 가상 머신으로 WM_MOUSEMOVED 메시지를 전송한다. 가상머신은 이벤트를 스몰토크 메시지 vmMouseMoved : with : 로서 적절한 pane 객체에 전송한다. argument는 무엇보다 마우스 위치를 나타내는 지점을 명시한다. 여러 번의 개입 메시지를 보낸 후 pane은 다음을 전송한다:

self triggerEvent: #mouseMoved: with: aPoint

이벤트를 트리거하는 것은 handlers 사전에서 이벤트명을 검색하고 관련 Messageㅡ이번 사례에서는 mouseIsAt : 이 되겠다ㅡ를 회복시키는 과정을 수반한다. 이후에 pane은 aPoint를 자신의 argument로 사용하는 Message를 실행한다.

버튼을 클릭한 이벤트로 돌아가, handlers 내에 이 이벤트에 대한 엔트리는 Message를 참조한다. 아래 표는 Message의 설정을 보여준다:

Aspect Value
selector #pasteButtonClicked
arguments (a Button)
receiver a TextEditor

클릭된 이벤트가 발생하면 위와 같이 Message가 평가된다. 그 결과, TextEditor 는 Button을 argument로 취하는 pasteButtonClicked : 를 수신한다.

다시 말하지만 위의 시나리오는 상세한 설명을 제공하진 않으며 해설을 목적으로 어느 정도 단순화시켰다. when : send : to : with : 의 수많은 변형체 중에는 다음도 포함된다:

  • when : #event send : #selectorㅡpane의 소유자가 (일반적으로 애플리케이션) 수신자임을 암시한다.
  • when : #event do : aBlockㅡ#event가 발생 시 블록이 평가되도록 야기한다.

Observer 패턴의 중점은, 애플리케이션이 특정 위젯 이벤트가 발생 시 작용할 실행을 명시한다는 점이다. 이는 어떠한 이벤트도 실제로 발생하기 전에 미리 이루어져야 한다. 위젯 이벤트가 발생하면 SASE 메커니즘은 실행작용이 (Message 또는 Block의 형태로) 발생하도록 보장한다.

비주얼웍스에서는 역할이 바뀐다는 사실을 주목한다. 비주얼 스몰토크 모델에서는 pane이 Subject, 애플리케이션 객체가 Observer이다. 비주얼웍스의 경우, 뷰가 Observer이고 애플리케이션 객체가 Subject에 해당한다.

기본이 되는 애플리케이션으로의 연결 pane (위젯)과 이벤트에 대한 디지토크(Digitalk) 접근법의 더 많은 예제는 Shafer, Herndon, Rozier (1993)과 LaLonde와 Pugh (1994a, 5장과 8장)을 참조한다. 하지만 위의 서적에서는 스몰토크/V (비주얼 스몰토크 이전)의 기존 버전을 다루고 있으므로 연결 메시지가 약간 틀릴 수 있다.

IBM 스몰토크에서 SASE 패턴의 사용

IBM 스몰토크에서 SASE 패턴은 비주얼 스몰토크와 거의 동일하게 사용된다. IBM 스몰토크에서는 CwWidget (비주얼스몰토크의 SubPane과 가장 비슷한) 가 다음 두 개의 메시지를 구현한다: addCallback:receiver:selector:clientData: 와 callCallbacks:callData:가 그것이다.

첫 번째 메시지는 비주얼 스몰토크의 when:send:to:with: 와 동등하다. 이것은 이벤트 (콜백 상수), 메시지의 수신자, 메시지, 이벤트가 발생 시 전송되어야 할 메시지에 대한 argument를 명시한다. CwWidget은 스스로 callCallbacks:callData: 를 전송함으로써 이벤트를 트리거한다. 이 메시지는 비주얼 스몰토크의 triggerEvent: 와 동등하다. 이벤트가 트리거되면 비주얼 스몰토크와 마찬가지로 애플리케이션 객체로 알림이 전송된다.

또한 IBM 스몰토크는 뷰 계층 내 객체들 간 통신에서도 거의 동일한 메소드 집합을 사용한다. CwWdiget는 addEventHandler:receiver:selector:clientData: 로 응답하는데 이 메시지는 이벤트가 (마우스 이동, 노출, 또는 키보드 이벤트) 기본적 윈도우 시스템이 관여할 때 호출되는 “이벤트 처리기”를 CwWidget로 추가하는 메시지이다. 보통 이벤트 처리기에서 마지막으로 발생하는 처리 중 하나는 콜백을 전송하는 일이다. 이러한 방식을 통해 마우스 클릭은 (예: 리스트에 있는) 이벤트 처리기를 트리거하고, 이는 콜백을 트리거하여, 애플리케이션 객체에 의해 리스트 선택으로 해석될 수 있다.

구현

당신은 자신만의 Observer 프레임워크를 구현하는 대신 스몰토크가 이미 제공하는 패턴을 구현해야 한다.

[디자인 패턴]에서는 Observer 패턴의 구현 방법에 대해 상당히 완성적인 논의를 제공한다. 다음 사항은 모두 스몰토크와 관련이 있다:

  1. 주제대상을 관찰자에게 매핑하는 경우. 이것은 앞에서 이미 구현한 바 있다. Object 내의 DependentsFields 사전과 Model 내의 dependents 인스턴스 변수는 이러한 매핑을 보관한다.
  2. 하나 이상의 대상을 관찰하는 경우. 객체가 하나 이상의 대상에 의존하는 것은 일반적이다. 종속자는 update:with:from: 내의 세 번째 파라미터를 검사함으로써 어떤 대상이 알림을 발송했는지 알아볼 수 있다. 그러나 대상이 업데이트 aspect를 중복하지 않는 이상 불필요하다. SASE 패턴은 이러한 상황을 더 쉽게 처리해준다.
  3. 누가 업데이트를 트리거하는가? 상태의 변경은 대상의 상태설정 오퍼레이션일까, 아니면 클라이언트 코드가 변경시킬까? 스몰토크는 주로 첫 번째를 선택한다. Aspect는 새로운 값을 파라미터로 받아들여 그 값을 주로 인스턴스 변수에 보관하는 setter 메소드를 거친다. 이 aspect가 변경 시 객체가 종속자에게도 통지해야 할 경우 setter 메소드로부터 변경 알림이 전송된다ㅡ예:
    Subject>>name: aString
    	"Set my value for name to aString
    	and announce this change to my dependents."
    	name := aString.
    	self changed: #name with: aString
    
  4. 삭제된 대상에 대한 참조가 그대로 유지되는 경우. 쓰레기 수집은 객체의 삭제를 더 투명하게 만들기 때문에 스몰토크에서 이러한 일이 발생하는 경우는 거의 없다. 대상의 수명은 주로 종속자의 수명길이와 비슷하기 때문에 먼저 삭제되지 않을 것이다. 종속자가 삭제되기 전에 대상이 삭제된 경우 종속자는 그 상태가 삭제되었기 때문에 적절한 동작을 멈출 것이다. 좀 더 공통적인 문제로는, 대상과 연결이 해제되지 않은 채 종속자가 삭제되는 경우이다. 그리고 나면 종속자가 사라진 것처럼 보이나 쓰레기로 수집되지도 않는다. 종속자가 수명이 다 되면 삭제 과정 중 일부가 대상에 대한 종속성을 끊어야 한다. 이는 주로 release에서 구현된다.
  5. 통지 이전에 주제대상 상태가 일관적으로 유지되도록 만드는 경우. 종속성 관계는 주로 단순하기 때문에 문제가 되진 않지만 복잡한 의존성의 경우 문제가 된다. 가장 중요한 고려사항은, 변경이 완전히 끝난 후에 종속자에게 통보하는 것이다. 위의 name: setter 메소드의 구현을 살펴보라. 인스턴스 변수를 먼저 설정한다; 완전히 끝나고 나면 종속자에게 통보한다.
  6. 관찰자-특정적 업데이트 프로토콜을 회피하기: push와 pull 모델. 다시 강조하지만, 종속자 관계는 단순한 편이라 관찰자-특정적 업데이트 프로토콜이 필요한 경우가 거의 없다. 단순한 종속성 관계의 경우 push와 pull 모델이 매우 비슷하다. Push 모델은 위에서 나타난 바와 같이 두 번째 파라미터가 새 값인 changed:with: 를 사용한다. Pull 모델은 변경사항을 알림과 동시 다음과 같이 관찰자가 원할 때 그것을 불러오도록 허용한다.
    Subject>>name
    	"Return my value for name."
    	^name
    
    Subject>>name: aString
    	"Set my value for name to aString
    	and announce this change to my dependents."
    	name := aString.
    	self changed: #name
    
    Observer>>update: anAspectSymbol
    	"See superimplementor."
    	anAspectSymbol == #name ifTrue: [self nameChanged].
    	...
    
    Observer>>nameChanged
    	"My subject's name has changed,
    	so get the new one to update mine."
    	| newName |
    	newName := self subject name.
    	self name: newName
    
  7. 관심대상에 대한 변경을 분명하게 명시하는 경우. [디자인 패턴] 편에서는 비주얼 스몰토크에 발견되는 예제를 소개한다. 이는 앞서 논한 비주얼웍스의 onChangeSend:to: 와 비슷하다.
  8. 복잡한 업데이트 의미를 캡슐화하는 경우. 이번 단락은 ChangeManager를 주제대상과 그것의 수많은 관찰자들 사이의 Mediator (287)로 구현하는 방법을 나타낸다. 스몰토크는 ChangeManager와 같은 클래스는 제공하지 않지만 [디자인 패턴]에 설명된 복잡한 종속성을 처리해야 하는 경우 클래스를 추가하면 된다.
  9. Subject 클래스와 Object 클래스를 결합하는 경우. 스몰토크에서는 분명히 이러한 기능을 수행한다. 두 프로토콜은 Object 에서 구현되어 어떠한 객체든 Subject, Observer, 또는 둘 다 될 수 있다.

예제 코드

Subject와 Observer 프로토콜은 Object에 이미 변경 / 업데이트 프로토콜로 구현되었다.

비주얼웍스 애플리케이션 모델

MVC에서 뷰는 그것의 모델의 종속자로서 모델에 일어나는 변경 내용을 통보받는다. Mediator (287) 패턴 단락에서 설명한 향상된 MVC에서 뷰는 그것의 애플리케이션 모델의 종속자이다. 애플리케이션 모델은 여전히 자신의 도메인 모델과 동일한 상태로 유지되어 종속자가 될 수 있도록 해야 한다. 이런 방식을 통해 애플리케이션 모델은 얼마든지 많은 뷰를 지원하고 (아예 없는 경우도 포함), 도메인 모델은 얼마든지 많은 수의 애플리케이션 모델을 지원할 수 있다 (아예 없는 경우도 포함). 다음 예제는 비주얼웍스에서 일반적인 ApplicationModel과 도메인 모델 쌍을 어떻게 구현하는지를 보여준다.

지역 코드, 지역 번호, 내선 번호를 모두 캡슐화하는 PhoneNumber라는 도메인 객체를 갖고 있다고 치자. 예제는 “212-555-1212 ext. 125”이다. 이의 클래스 정의와 인스턴스 생성은 다음과 같다:

Object subclass: #PhoneNumber
	instanceVariableNames:: 'areaCode localNumber extension'
	classVariableNames: ''
	poolDictionaries: ''

PhoneNumber>>initAreaCode: areaString localNumber: localString
extension: extensionString
	"Initialize the receiver with the values
	areaString, localString, and extensionString."
	areaCode := areaString.
	localNumber := localString.
	extension := extensionString.
	^self

PhoneNumber class>>areaCode: areaString localNumber:
localString extension: extensionString
	"Create and return an instance of PhoneNumber with the
	values areaString, localString, and extensionString."
	^self new
		initAreaCode: areaString
		localNumber: localString
		extension: extensionString

PhoneNumber의 한 가지 특징은 모든 유효한 지역 코드 리스트를 알고 있다는 것이다:

PhoneNumber>>validAreaCodes
	"Return a list of all valid area codes."
	| validCodes |
	... code to gather area codes from a database ...
	^validCodes

PhoneNumber의 또 다른 특징으로는 전화번호가 내선 번호를 갖고 있지 않을 경우 extension 변수가 nil이 된다는 점이다. 전화번호를 표시하는 객체를 보여주고 사용자가 이를 편집하도록 허용하는 윈도우 창이 (예: Employee 또는 Customer를 편집하는 창) 필요하다고 치자. 전화번호 위젯은 편집을 수월하게 하기 위해 세 가지 구분된 문자열 필드에 전화번호의 세 가지 부분을 표시한다. 지역 코드 필드는 사실상 콤보상자 위젯으로서 사용자가 지역 코드를 유효화하도록 제한한다.

전화번호는 여러 개의 다른 윈도우 창으로 표시할 필요가 있으므로 윈도우가 재사용할 수 있는 고유의 ApplicationModel에 전화번호 편집기를 구현할 것이다. 이를 클래스 PhoneNumberAM이라 부르겠다. 캔버스에 지역 코드를 위한 콤보상자와 번호 및 내선 번호를 위한 두 개의 필드를 그린다. 지역 코드에 필드가 아닌 콤보상자를 사용하는 이유는 사용자가 유효한 지역 코드에서 선택하도록 제한하기 위해서이다.

아래 표는 전화번호 창의 위젯 설정을 보여준다. 이를 적용하고 나면 ApplicationModel의 서브클래스인 PhoneNumberAM로서 클래스를 설치하고, 그 인스턴스 변수와 메소드를 정의할 것이다.

Widget Purpose Property Name Property Value
Combobox Area code aspect #areaCodeHolder
choices #validCodesHolder
type String
Input field Local number aspect #localNumberHolder
type String
Input field Extension aspect #extensionHolder
type String

이제 기본적인 ApplicationModel, PhoneNumberAM가 있는데, 이것은 전화번호는 표시할 수 있으나 PhoneNumber 도메인 객체에 관해서는 아는 바가 없다. 따라서 아래 표시된 볼드체 타입에서와 같이, 인스턴스 변수 domainHolder를 추가하기 위해 PhoneNumberAM에 코드 브라우저를 사용하여 그것의 클래스 정의를 변경하고자 한다:

Object subclass: #PhoneNumberAM
	instanceVariableNames: 'domainHolder areaCodeHolder
ValidCodesHolder localNumberHolder extensionHolder'
	classVariableNames: ''
	poolDictionaries: ''

PhoneNumberAM>>domainHolder
	"Get and return the value of domainHolder."
	^domainHolder

PhoneNumberAM>>setDomainModel: aModel
	"Set the domain model to aModel."
	self domainHolder value: aModel.
	^self

PhoneNumberAM class>>newWithDomainModel: aModel
	"Create and return a new instance of PhoneNumberAM
	whose domain model is aModel."
	^self new setDomainModel: aModel

PhoneNumberAM class >>openWithDomainModel: aModel
	"Open a new instance of PhoneNumberAM
	whose domain model is aModel."
	^(self newWithDomainModel: aModel) open

이러한 변수들에 대한 구성을 커스터마이즈하기 위해 initialize를 구현할 것이다. domainHolder는 ValueHolder로서 도메인 객체를 유지하는데, 이번 사례에서는 PhoneNumber가 되겠다. 다른 4개의 변수는 AspectAdaptor들로, ValueHolder와 같은 객체이지만 subject라 부르는 다른 객체로부터 그 값을 aspect로 추출하는 방법을 알고 있다. (AspectAdapter와 다른 ValueModel들이 어떻게 작동하는지에 관한 정보는 Adapter (105)와 Wollf, 1995b를 참조한다.) 이번 사례에서는 subject가 도메인 객체이므로 PhoneNumber이고 domainHolder에서 접근한다:

PhoneNumberAM>>initialize
	"See superimplementor."
	super initialize.
	domainHolder := nil asValue.
	areaCodeHolder := self adaptorForAspect: #areaCode.
	validCodesHolder := self adaptorForAspect: #validAreaCodes.
	localNumberHolder := self adaptorForAspect: #localNumber.
	extensionHolder := self adaptorForAspect: #extension.
	^self

PhoneNumberAM>>adaptorForAspect: aSymbol
	"Create and return an AspectAdaptor for aSymbol."
	^(AspectAdaptor
		subjectChannel: self domainHolder
		sendsUpdates: true)
		forAspect: aSymbol

adaptorForAspect:가 AspectAdaptor를 어떻게 설정하는지를 주목하자: 어댑터가 domainHolder로부터 주제대상을 얻으므로 주제대상이 도메인 객체가 될 것을 의미하며, 주제대상이 업데이트를 전송할 것으로 기대한다. 아래에서 살펴보듯 이러한 설정은 중요하다 . 각 AspectAdaptor는 aspect 설정을 갖고 있으며 자신의 대상이 aspect의 이름을 한 getter와 setter 메소드를 가질 것으로 예상한다. Aspect를 실제로 aspect라 부른다면 getter와 setter 메소드는 aspect와 aspect: 라 불릴 것이다. AspectAdaptor가 자신의 대상이 업데이트를 전송할 것으로 기대할 경우 업데이트 aspect는 #aspect와 같이 aspect의 이름을 딴 기호라고 예상할 것이다.

따라서 PhoneNumber는 PhoneNumberAM의 AspectAdaptor들을 지원하기 위해 여러 개의 getter와 setter 메소드를 가질 필요가 있다. 이것은 이미 validAreaCodes getter 메소드를 구현하며 validAreaCodes: setter를 필요로 하지 않는데 그 이유는 콤보상자의 리스트가 setter 메시지를 절대 전송하지 않을 것이기 때문이다. 3개의 다른 aspect에 getter와 setter 메시지를 구현할 필요가 있다.

getter 메소드는 매우 단순하게 구현될 것이다; 인스턴스 변수의 값을 단순히 반환할 뿐이다. setter 메소드는 이보다 좀 더 많은 일을 한다. 이 메소드는 인스턴스 변수의 값만 설정하는 것이 아니다; 인스턴스 변수 값이 변경되었다는 사실을 도메인 객체의 종속자에게 알리는 일도 한다. AspectAdaptor가 sendsUpdates를 true로 가지려면 어떻게 하는지 기억하는가? 이는 어댑터가 도메인 객체로부터 변경 알림을 들어야 함을 의미하므로 도메인 객체의 setter는 그 알림을 제공해야 한다.

PhoneNumber>>areaCode
	"Get and return the value of areaCode."
	^areaCode

PhoneNumber>>areaCode: aString
	"Set the value of areaCode to aString."
	areaCode := aString.
	self changed: #areaCode with: aString

PhoneNumber>>localNumber
	"Get and return the value of localNumber."
	^localNumber

PhoneNumber>>localNumber: aString
	"Set the value of localNumber to aString."
	localNumber:= aString.
	self changed: #localNumber with: aString

PhoneNumber>>extension
	"Get and return the value of extension."
	^extension

PhoneNumber>>extension: aString
	"Set the value of extension to aString."
	extension:= aString.
	self changed: #extension with: aString

종속자에게 수많은 변경 통지를 제공하기 위해 PhoneNumber를 구현해 보았다. 앞서 논의에서는 중요한 종속성 알림을 가진 Object의 서브클래스는 Model의 서브클래스일 때 더 효율적으로 실행할 것이라고 설명한 바 있다. 도메인 객체인 PhoneNumber는 Object의 서브클래스로서 중요한 종속성 알림을 가지므로 도메인 모델임과 동시 Model의 서브클래스여야겠다.

Model subclass: #PhoneNumber
	instanceVariableNames: 'areaCode localNumber extension'
	classVariableNames: ''
	poolDictionaries: ''

이제 PhoneNumber를 생성하고 다음과 같이 편집기를 열 수 있다:

| phoneNumber |
phoneNumber:= PhoneNumber
		areaCode: '212'
		localNumber: '555-1212'
		extension: nil.
PhoneNumberAM openOnDomainModel: phoneNumber

이 윈도우 창은 거의 작동하지만 작은 버그가 하나 있다. extension aspect는 nil이 될 수 있지만 내선 번호 필드는 String을 기대하므로 nil은 작동하지 않는다. 우리가 필요한 것은 빈 문자열처럼 nil을 String로 변환하는 일이다. 그러나 내선 번호가 nil일 경우에만 적용해야 한다; String일 경우 그대로 사용해야 한다.

필드는 이미 ValueModel를 (AspectAdaptor) 사용하고 있으므로 nil을 String으로 쉽게 변환하는 방법은 이러한 목적으로 설계된 또 다른 ValueModel인 TypeConverter를 사용하는 것이다. 이는 onStringValue: 메시지를 통해 생성된 String이 값이 되도록 보장하는 형태를 가진다. 따라서 PhoneNumberAM에 대한 초기화 코드는 AspectAdaptor의 값을 변환하기 위해 TypeConverter 중 하나를 사용해야 한다. 변경된 메소드는 다음과 같으며, 변경된 부분을 볼드체로 표시하였다:

PhoneNumberAM>>initialize
	"See superimplementor."
	| adaptor |
	super initialize.
	domainHolder := nil asValue.
	areaCodeHolder := self adaptorForAspect: #areaCode.
	validCodesHolder := self adaptorForAspect: #validAreaCodes.
	localNumberHolder := self adaptorForAspect: #localNumber.
	adaptor := self adaptorForAspect: #extension.
	extensionHolder := TypeConverter onStringValue: adaptor.
	^self

Observer로서의 AspectAdaptor

PhoneNumber와 PhoneNumberAM에 대한 코드는 비주얼웍스에서 사용되는 Observer 패턴을 설명한다. 주제대상은 PhoneNumber의 인스턴스이다. 그 변경 메시지는 각각의 setter 메소드로서 changed:with: 를 전송한다. 각 AspectAdaptor는 관찰자이다. 그의 업데이트 메시지는 update : with : from : 에 대한 AspectAdaptor 의 구현부이다.

AspectAdaptor가 어떻게 종속자로서 구현되는지를 살펴보자. AspectAdaptor 는 ProtocolAdaptor의 서브클래스이며, 일부 종속성 행위는 슈퍼클래스에서 구현된다.

ProtocolAdaptor>>hookupToSubject는 AspectAdaptor를 그 주제대상의 종속자로 등록하는 코드를 포함한다. 이는 subjectSendsUpdates가 true로 설정되었을 경우에만 해당한다; AspectAdaptor가 “sendsUpdates: true”를 필요로 한 이유이다:

ProtocolAdaptor>>hookupToSubject
	"Add the receiver as a dependent of the receiver's subject."
	subjectSendsUpdates
		ifTrue: [subject notNil
			ifTrue: [subject addDependent: self]]

그리고 나면 AspectAdaptor는 subject의 변경에 대한 통지를 받기 위해 update:with:from: 를 구현한다. 변경을 알리는 주제대상이 어댑터의 subject일 경우와 통보되는 변경이 어댑터의 aspect에서 일어난 변경일 경우, 어댑터의 value는 변경되고 이를 자신의 dependents로 알리면서 끝난다:

AspectAdaptor>>update: anAspect with: parameter from: sender
	"Propagate change if the sender is the receiver's subject
	and anAspect is the receiver's aspect."
	(sender == subject and: [anAspect == self forAspect])
		ifTrue:
			[dependents
				update: #value with: parameter from: self]
		ifFalse:
			[super
				update: anAspect with: parameter from: sender]

우리는 PhoneNumber에 Subject 행위를 구현해야 했다는 점을 주목하자. 반면 AspectAdaptor는 Observer 행위가 이미 구현되어 있었다. 비주얼웍스에서는 이것이 일반적이다. 우리는 Subject를 직접 구현해야 하지만 비주얼웍스는 이미 Observer를 구현하고 있다.

모델-뷰-컨트롤러에서 Observer 패턴의 사용

모델-뷰-컨트롤러 프레임워크는 Observer 패턴의 처음이자 가장 잘 알려진 예제이다. 스몰토크-80에 도입되어 비주얼웍스에선 계속 사용되고 있다. Observer 패턴의 주요 예제는 ValueModel들과 그 값을 바탕으로 한 하위뷰 사이에 존재한다 (Woolf, 1995a). 예를 들어, 입력 필드와 콤보상자의 필드 부분에 대한 하위뷰 클래스는 InputFieldView와 그 서브클래스 ComboBoxInputFieldView이다.

ValueModel은 새 값을 받으면 change: 를 전송함으로써 종속자에게 그 사실을 알린다.

ValueModel>>value: newValue
	"Set the currently stored value, and notify dependents."
	self setValue: newValue.
	self changed: #value

하위뷰가 변경을 통지받으면 이는 모델의 새 값을 얻어 표시한다. 하위뷰는 addDependent: 를 이용해 종속자로 스스로를 등록함으로써 모델의 변경을 통지받는다. 이것은 모델을 가질 수 있는 모든 뷰에 대한 (예: View) 슈퍼클래스 DependentPart에서 구현된다:

DependentPart>>setModel: aModel
	"Set the receiver's model to be aModel."
	( model == aModel )
		ifFalse: [
			model isNil
				ifFalse: [ model removeDependent: self ].
			(model := aModel) isNil
				ifFalse: [ model addDependent: self ] ].

값을 기반으로 한 하위뷰는 하나의 모델만 갖고 있고 모델에는 하나의 aspect만 있으므로 하위뷰는 어떤 변경을 알리는지 확인하지도 않는다.

하위뷰는 모델의 값이 변경된 것이라고 가정하여 새 값을 얻고자 한다. 하위뷰는 이를 위해 update: 를 구현함으로써 변경 알림을 듣는다:

InputFieldView>>update: aSymbol
	"The receiver's model has changed its text.
	Update the receiver."
	self updateDisplayContents

이번 예제에서 ValueModel는 주제대상이고 하위뷰가 관찰자이다. 이 ValueModel은 ApplicationModel에 있으며, 둘 다 도메인 모델에 관찰자와 하위뷰에 대한 대상이 될 수 있다는 점을 주목한다.

비주얼웍스는 이미 ValueModel 클래스나 InputFieldView와 같은 하위뷰 클래스를 모두 구현하기 때문에 당신은 이러한 예제의 Observer 패턴을 사용하기 위해 코드를 별도로 구현할 필요가 없다.

비주얼 스몰토크와 IBM 스몰토크에서 Observer 패턴의 사용

비주얼 스몰토크와 IBM 스몰토크에서 Observer 패턴 사용의 예제 코드는 Mediator (287)의 예제 코드를 참조한다.

알려진 스몰토크의 사용예

각 방언은 스몰토크-80에서 하나 또는 두 가지의 변경 / 업데이트 프로토콜 변형체를 구현하지만 이러한 구현은 언어에 기본적이어서 그 외에는 필요가 없을 것이다. 대부분 스몰토크 애플리케이션은 수많은 변경 / 업데이트 프로토콜 사용을 포함하며, 각 애플리케이션은 알려진 Observer 패턴의 사용예이다. Observer의 SASE 변형체 사용 또한 매우 흔하다. Observer 패턴은 스몰토크에 매우 기본적이라 언어 특징에 가깝다.

관련 패턴

비주얼웍스의 ValueModel 계층구조 내 다수의 클래스는 Observer 클래스이다. 그 중 일부는 Adapter (105) 클래스이기도 하며, 그의 주제대상은 Adaptee이기도 하다. 나머지는 Decorator (161) 클래스로서, 그 주제대상은 Component이기도 하다.

애플리케이션 모델, 즉 자신의 도메인 모델에 대한 관찰자는 위젯의 Mediator (287)에 해당한다.

값 기반의 하위뷰, 다시 말해 자신의 ValueModel에 대한 관찰자 또한 ValueModel을 Strategy (339)로서 사용해 값에 접근한다. ValueModel 또한 그 값을 표시하기 위해 하위뷰를 Strategy로 사용한다.

Notes

  1. 사실상 키워드 선택기를 이름으로 가지는 이벤트의 경우 (예: mouseMoved :), 해당하는 값은 LinkMessage의 인스턴스, 즉 Message의 서브클래스이지만 예제를 단순화시키기 위해 세부 내용은 무시한다.