노무현 대통령 배너

Observer Pattern 옵저버 패턴을 액션스크립트로 컨버팅 – Head First Design Pattern

by on 5.19, 2009, under Design Pattern

the_observerHead First Design Pattern 의 제 2 장 내용인 옵저버 패턴(Observer Pattern) 으로 들어가 보겠습니다. 역시 이번에도 자바 코드를 액션스크립트로 컨버팅 하였습니다.

다음은 책 본문에 있는 옵저버 패턴에 대한 설명입니다.

“옵저버 패턴 – 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의합니다.

사실 위의 정의만 보고는 뭐가 뭔지 알쏭달쏭한데요, 차근차근 풀어나가보겠습니다.

먼저 옵저버 패턴에 등장하는 세 등장인물을 소개하겠습니다.

  1. 옵저버 패턴의 중심에서 옵저버들에게 통신 내용을 주는 역할을 하는 객체를 주제 (Subject) 라고 합니다. 다른말로는 주체라고도 할 수 있습니다. 주제를 일컫는 말에는 이외에도 다양하게 있습니다. 액션스크립트에서 익숙한 용어로는 디스패처(dispatcher)도 주제를 표현하는 다른 용어이고, supplier, notifier, publisher, server, service, provider 와 같이 출판자, 공급자, 생산자, 배포자의 의미를 가진 단어도 주제의 다른 단어 형태입니다.
  2. 한편 옵저버는 클라이언트, 소비자, 구독자, 리스너 등으로 표현할 수 있습니다. 주제가 던져주는 정보를 받아보는 애들이죠. 옵저버 패턴이라는 명칭 때문에 옵저버가 중요한 역할을 하고 있는것 같지만 실제로 옵저버가 하는 일은 아무것도 없고 모든 일은 주제가 합니다. 옵저버는 주제가 던져주는 내용을 보기만 할 수 있습니다.
  3. 그리고 주제가 옵저버들에게 돌리는 내용은 info(정보) 입니다. 주제는 자신이 던져준 info 를 옵저버들이 사용하든 안하든 관심이 없습니다. 다만 옵저버 목록에 있다면 info 를 줄 뿐이죠.

 

책의 예제는 측정된 기상 정보를 여러개의 단말기에 일제히 보내는 시스템을 구현하고 있습니다.

옵저버 인터페이스를 살펴보겠습니다.

0
1
2
3
4
5
6
package
{
	public interface IObserver
	{
		function update( temp:Number, humidity:Number, pressure:Number ):void
	}
}

온도, 습도, 기압 세가지 측정 값을 구상 클래스에 던져주는 역할을 합니다.

그리고 IDisplayElement 클래스는 info가 옵저버에 도착해서 update 되었을 때 호출하는 display 메소드가 있습니다.

0
1
2
3
4
5
6
package
{
	public interface IDisplayElement
	{
		function display():void
	}
}

다음은 옵저버 클래스 입니다.
이론적으로 옵저버가 되기 위해서는 IObserver 인터페이스만 구현하면 되지만 예제에서 나온 기상 스테이션의 경우 info 가 notify 될 때 말고도 평상시에 info를 보고싶어할 경우도 있을 수 있으므로 display() 메서드를 인터페이스로 빼서 언제든 인터페이스 메소드만 호출하면 모든 옵저버 객체에서 자신이 표시해야 할 값들을 표시하게 합니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package
{
	public class CurrentConditionsDisplay implements IObserver, IDisplayElement
	{
		private var temperature:Number;
		private var humidity:Number;
		private var weatherData:ISubject;
 
		public function CurrentConditionsDisplay ( $weatherData:ISubject )
		{
			this.weatherData = $weatherData;
			weatherData.registerObserver( this )
		}
 
		public function update( $temperature:Number, $humidity:Number, $pressure:Number ):void
		{
			this.temperature = $temperature;
			this.humidity = $humidity;
			display();
		}
 
		public function display():void
		{
			trace( "현재 온도 :", temperature, "도 / 습도 :", humidity )
		}
	}
}

클래스 정의를 하고 있는 라인을 보면 옵저버 구상 클래스는 IObserver, IDisplayElement 두 개의 인터페이스를 구현하고 있음을 알 수있습니다.

그럼 이제 주제 인터페이스를 보겠습니다.

0
1
2
3
4
5
6
7
8
package
{
	public interface ISubject
	{
		function registerObserver( o:IObserver ):void;
		function removeObserver( o:IObserver ):void;
		function notifyObservers():void;
	}
}

주제 인터페이스에서는 옵저버를 등록할 수 있는 메서드, 등록된 옵저버를 해제할 수 있는 메서드, 그리고 새로운 info 가 도착했음을 알리는 메서드가 있습니다.

이 인터페이스를 구현하는 오늘의 주인공 주제 구상 클래스를 보도록 하겠습니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package
{
	public class WeatherData implements ISubject
	{
		//옵저버들을 배열로 저장할 인스턴스선언과 세 가지 측정값을 저장하는 변수선언
		private var observers:Array;
		private var temperature:Number;
		private var humidity:Number;
		private var pressure:Number;
 
		public function WeatherData()
		{
			observers = new Array()
		}
 
		//옵저버를 등록, ISubject 인터페이스를 구현함.
		public function registerObserver( o:IObserver ):void
		{
			observers.push( o )
		}
 
		//등록된 옵저버를 등록을 해제, ISubject 인터페이스를 구현함.
		public function removeObserver( o:IObserver ):void
		{
			var i:uint = observers.indexOf( o )
			if ( i >= 0 )
			{
				observers.splice( i, 1 )
			}
		}
 
		//각 옵저버들에게 info를 전달, ISubject 인터페이스를 구현함.
		public function notifyObservers():void
		{
			for ( var i:uint = 0; i < observers.length; i++ )
			{
				var observer:IObserver = observers[ i ];
				observer.update( temperature, humidity, pressure );
			}
		}
 
		//측정값(info)이 변경되었음을 경우 notifyObservers()를 호출
		//그러나 이 메서드는 현재 변경에 대한 코드가 없는 상태입니다. 메서드를 전달만 하고 있네요.
		public function measurementsChanged():void
		{
			notifyObservers();
		}
 
		//호스트코드에서 info를 주제에게 던질때 사용하는 메서드
		public function setMeasurement( $temperature:Number, $humidity:Number, $pressure:Number ):void
		{
			this.temperature = $temperature;
			this.humidity = $humidity;
			this.pressure = $pressure;
			measurementsChanged();
		}
	}
}

각 메서드에 대한 설명은 코드 부분에 주석으로 추가하였습니다.

그럼 호스트 코드에서는 어떻게 사용하는지 볼까요?

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package
{
	import flash.display.Sprite;
	import flash.utils.describeType;
 
	public class Main extends Sprite
	{
		public function Main()
		{
			//WeatherData 객체를 만들어 각각의 옵저버 구상 객체에 넣어주었습니다.
			var weatherData:WeatherData = new WeatherData();
			var currentDisplay1:CurrentConditionsDisplay = new CurrentConditionsDisplay( weatherData );
			var currentDisplay2:CurrentConditionsDisplay = new CurrentConditionsDisplay( weatherData );
			var currentDisplay3:CurrentConditionsDisplay = new CurrentConditionsDisplay( weatherData );
 
			weatherData.setMeasurement( 20, 65, 30.4 );
				//출력 : 현재 온도 : 20 도 / 습도 : 65
				//출력 : 현재 온도 : 20 도 / 습도 : 65
				//출력 : 현재 온도 : 20 도 / 습도 : 65
 
			weatherData.removeObserver( currentDisplay1 ); //이렇게 1번 디스플레이를 옵저버 리스트에서 빼면
			weatherData.setMeasurement( 22, 70, 29.2 ); //1번 디스플레이를 뺀 2, 3번만 디스플레이 됩니다.
				//출력 : 현재 온도 : 22 도 / 습도 : 70
				//출력 : 현재 온도 : 22 도 / 습도 : 70
 
			weatherData.registerObserver( currentDisplay1 ); //이렇게 다시 넣어줄 수도 있겠죠.
			weatherData.setMeasurement( 28, 90, 29.2 ); // 2, 3, 1 번 모두 디스플레이 됩니다.
				//출력 : 현재 온도 : 28 도 / 습도 : 90
				//출력 : 현재 온도 : 28 도 / 습도 : 90
				//출력 : 현재 온도 : 28 도 / 습도 : 90
		}
	}
}

이렇게 주제 객체가 현재 배열로 가지고 있는 옵저버 목록에 있는 객체들에게 update() 메서드를 호출해서 정보를 전달해 주고, 필요에 따라 옵저버 객체들을 옵저버 목록에서 빼거나 다시 등록할 수 있습니다.

 

Observer Pattern Example 액션스크립트 코드 다운로드 (200) 

 

그런데 헤드퍼스트 디자인 패턴의 옵저버 패턴 예제에는 잘못된 부분이 하나 있습니다.
호스트코드를 보면, 옵저버가 되는 currentDisplay1 객체를 생성할 때에 weatherData 를 레퍼런스로 넘기고 있는 부분입니다.

0
var currentDisplay1:CurrentConditionsDisplay = new CurrentConditionsDisplay( weatherData );

옵저버 구상 클래스(CurrentConditionsDisplay) 의 생성자와 변수에도 주제의 레퍼런스를 저장하도록 하고 있는걸 볼 수 있습니다.

97페이지의 바보같은 질문은 없습니다 코너에 보면 그에대한 이유를 설명해 놓았는데요,

Q: Subject 에 대한 레퍼런스는 왜 저장하죠? 생성자 말고 다른데서는 쓸 일이 없지 않나요?
A: 예 올바른 지적입니다. 하지만 나중에 옵저버 목록에서 탈퇴해야 한다면 주제 객체에 대한 레퍼런스를 저장해 두면 유용하게 쓸 수 있을 것입니다.

그러나 책의 이러한 설명은 옵저버 패턴 원칙에서 벗어나는 내용입니다. 주제는 옵저버 리스트를 가지고 있지만, 옵저버는 주제객체를 알 수 없어야 합니다. 옵저버가 주제객체를 아는 순간 옵저버는 더이상 옵저버가 아니게 되는 것이죠. 이런 형태는 옵저버 패턴이라 부르지 않고 그저 객체들간 통신을 하고 있는 것입니다.

그러므로 이러한 옵저버 패턴의 잘못된 적용을 바로 잡기 위해 수정된 코드를 포함하여 다음 포스트를 작성하겠습니다. Observer Pattern 옵저버 패턴의 수정 – 옵저버는 주제를 몰라요.

이 글을 복사해서 퍼가시는건 허용하지 않습니다. 글의 주소를 다른곳에 알려주시는 것은 환영합니다.

관련된 글

:, , , , , , , , , , , , , ,

6 Comments for this entry

  • 봉봉이No Gravatar

    종선님 좋은 포스팅 감사드립니다. 디자인 패턴 관련 포스팅은 이제 출근하자마자 보는 하루 일과가 되었네요. 앞으로도 화이팅!!

    • 세계의끝No Gravatar

      패턴관련 포스팅은 뭐 몇개 안되는데요…
      그리고 중요한 패턴은 이미 다 짚었습니다. 앞으로 남은건 패턴 몇 개 추가와 응용편 정도가 되겠네요. ^^

  • No Gravatar

    음. 제 책(액션3 디자인패턴)에는 옵져버패턴이 없네요. 그외에도 몇개 더 없지만.
    근데 MVC(모델-뷰-컨트롤러)패턴 항목을 읽다보니 모델과 뷰의 역할과 거의 비슷한것 같기도 하구요.
    모델(데이타)은 뷰(디스플레이)와 컨트롤러(UI)를 몰라야 하며 독립적이어야 하고
    컨트롤러의 입력에 따라 모델을 업데이트시키고, 모델이 업데이트되면 자동으로 뷰가 업데이트되어야 하며 뷰는 컨트롤러와 강한 결합이고 …머 대충 이런 내용인데

    좀 다른게 옵저버라는게 지켜보는건데… 책에선 뷰가 모델을 항상 알고 있고 모델의 업데이트를 청취하고 있다가 변경되면 스스로 업데이트되는건데..(모델에서 dispatchEvent)
    내가 헷갈리고 있는건지 아니면 명칭만 다르게 쓰이고 있는지도 궁금하네요.

    예전에 질문드린 파이터(비행기)와 키보드클래스간 관계에 대해 물어본것과도 연관이 있는 것 같아서 적어봅니다.
    암턴 잘 읽고 갑니다.

    • 세계의끝No Gravatar

      “액션스크립트 3.0 디자인 패턴” 책의 297 페이지에 보면, 옵저버 패턴을 다루지 않은 이유가 설명되어 있죠.
      AS3.0 의 이벤트 모델이 옵저버 패턴의 이슈를 해결해 줬다고 하는 부분 입니다.
      이 말은 어느정도는 맞는 말이지만 좀 빡빡하게 따지고 들어가면 무리가 있는 말이기도 합니다.
      AS3.0의 이벤트 모델에서 옵저버의 역할을 하는 객체가 무슨 일을 하려면 디스패처(주제)를 알고 있어야 하거나, 최소한 event.target 으로 주제를 참조해서 내부의 데이터에 접근해야 하는데, 객체가 주제의 구조를 모른다면 무용지물이 됩니다. 이 포스트의 마지막에 보시는 오류 내용과 같이 옵저버가 주제를 알아야 하는 상황이 되 버리는거죠. (이 부분은 히카님의 블로그를 참고해 보세요. http://www.diebuster.com/?p=1072 )

      그리고 mvc 패턴과 옵저버 패턴은 일견 비슷해 보이기도 하지만 사용 목적이 다르다고 할 수 있을겁니다. mvc 는 결국 다양한 view 를 구현하기 위함인데, 옵저버 패턴에는 옵저버가 어떻게 view 를 구현하든 옵저버 패턴 자체와는 관계 없는 내용인거죠. 옵저버패턴은 mvc 의 세가지 요소중 모델 부분만 동일하다 할 수 있을겁니다.
      “액션스크립트 3.0 디자인 패턴” 책의 82페이지의 “주의” 내용 처럼, mvc 를 순수한 디자인 패턴으로 취급하기에는 다소 무리가 있을지도 모르고요. ^^
      (헤드퍼스트 디자인패턴 책에는 mvc 를 다루고 있지 않습니다)

      • No Gravatar

        답글 감사드립니다. 책 중간쯤 보고 있는 중인데다 연습코딩하다가 다시보고 다시보고 ㅡㅡ;
        뭐 이러고 있는중입니다.
        패턴이란게 딱히 이건 무슨 패턴이다 라고 굳이 구분할 필요까진 없다고 생각은 하는데
        최소한 의사소통할 정도는 알고 있어야 할것 같아서 대략적인 개념이 파악될 때까지 응용 코딩을 자유자재로 할 때까지 반복중입니다. 근데 아직은 많이 막히네요 ㅋ
        수고하시구 패턴 포스팅도 자주 해 주세요 ^^

    • 세계의끝No Gravatar

      예제를 응용해서 만드실 정도면 훌륭한데요?
      패턴 사용법 자체는 책에도 나와 있고, 그리 어려운 것이 아닙니다만, 그것을 자연스럽게 녹여내고 적재적소에 사용하는것이 정말 어려운거죠. ^^

Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Meta