옵저버 패턴
옵저버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.
목차 |
[편집] 구현
이 패턴의 핵심은 옵저버 또는 리스너(listener)라 불리는 하나 이상의 객체를 등록하거나 자신을 등록시킨다. 그리고 관찰되는 객체(또는 주제)에서 발생하는 이벤트를 전달한다.
UML 다이어그램으로는 아래처럼 표현된다.
이벤트가 발생하면 각 옵저버는 콜백(callback) - 이것은 다이어그램에 표시된 옵저버 클래스의 가상 함수이거나 함수 포인터이다. 함수포인터는 흔히 함수 객체 또는 함수자(functor)로 불린다. 리스너로 등록된 객체에 인자를 전달한다 - 을 받는다. notify 함수는 옵저버에 의해 쓰여진 인자값을 전달할 수도 있다.
각각의 파생 옵저버는 notify 함수를 구현하고 이벤트가 발생했을 때 처리할 각자의 동작을 정의해야 한다.
주제는 일반적으로 등록(register), 제거(unregister) 메서드가 있는데, 전자는 새로운 리스너를 등록하고 후자는 이벤트가 발생할 때 알려줄 객체 목록에서 옵저버를 빼버린다. 그리고 임시로 작동을 멈추거나 재개하는 메서드가 있어서 이벤트가 계속해서 있을 때 홍수같이 발생하는 요청을 제어한다.
옵저버 패턴이 많이 쓰인 시스템에서는 순환 실행을 막는 메카니즘이 필요하다. 이벤트 X가 있을 때 A가 B를 갱신한다고 가정해보자. B는 이 처리를 위해 A를 갱신한다. 그래서 다시 A로 하여금 이벤트 X를 발생하게 한다. 이같은 상황을 막기 위해 이벤트 X가 처리된 후에는 A가 이것을 다시 발생시키지 않는 방법이 요구된다.
[편집] 대표적인 사례
옵저버 패턴의 대표적인 이용 사례는 다음과 같다.
- 외부에서 발생한 이벤트(사용자 입력같은)에 대한 응답. 이벤트 기반 프로그래밍을 참조하라.
- 객체의 속성 값 변화에 따른 응답. 종종 콜백은 속성 값 변화를 처리하기 위해 호출될 뿐 아니라 속성 값 또한 바꾼다. 때때로 이벤트 연쇄의 원인이 될 수 있다. 속성 변화를 감시하고, 다른 속성을 바꾸는 옵저버 패턴 사용에 대한 의견을 보려면 이 글을 참조하라.
옵저버 패턴은 모델-뷰-컨트롤러(Model-View-controller, MVC) 패러다임과 자주 결합된다. 옵저버 패턴은 MVC에서 모델과 뷰 사이를 느슨히 연결하기 위해 사용된다. 대표적으로 이벤트를 통보받는 모델 옵저버가 실행하는 트리거는 뷰의 내용을 바꾸게 된다.
[편집] 사례
옵저버 패턴은 여러 프로그래밍 라이브러리와 시스템에서 구현되는데 거의 모든 GUI 툴킷도 해당한다.
이 패턴이 쓰인 주목할 만한 사례 몇 가지:
- Java Swing 라이브러리는 이벤트 관리를 위해 옵저버 패턴을 광범위하게 사용했다.
- Boost.Signals, 시그널/슬롯(signal/slot) 모델을 제공하기 위해 C++ STL을 확장했다.
- C++ 프레임워크의 시그널/슬롯 모델인 QT
- libsigc++, C++ 시그널링 템플릿 라이브러리
- GLib, C로 객체와 시그널/콜백을 구현. 이 라이브러리는 다른 프로그래밍 언어에서 쓰기 위해 많은 바인딩 방법을 갖고 있다)
- 옵저버 디자인 패턴 탐험, C#과 비주얼 베이직 닷넷으로 대표 기법과 이벤트 패턴(the Event pattern)을 사용했다.
[편집] 예제
아래 예제는 자바언어 및 C#으로 구현된 옵저버 패턴의 예이다.
[편집] 자바
/* 파일명 : EventSource.java */ package obs; import java.util.Observable; // 이 부분이 옵저버입니다. import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class EventSource extends Observable implements Runnable { public void run() { try { final InputStreamReader isr = new InputStreamReader( System.in ); final BufferedReader br = new BufferedReader( isr ); while( true ) { final String response = br.readLine(); setChanged(); notifyObservers( response ); } } catch (IOException e) { e.printStackTrace(); } } }
/* 파일명: ResponseHandler.java */ package obs; import java.util.Observable; import java.util.Observer; /* 여기가 처리기 */ public class ResponseHandler implements Observer { private String resp; public void update (Observable obj, Object arg) { if (arg instanceof String) { resp = (String) arg; System.out.println("\nReceived Response: "+ resp ); } } }
/* 파일명 : myapp.java */ /* 여기서부터가 프로그램 시작점 */ package obs; public class MyApp { public static void main(String args[]) { System.out.println("Enter Text >"); // create an event source - reads from stdin final EventSource evSrc = new EventSource(); // create an observer final ResponseHandler respHandler = new ResponseHandler(); // subscribe the observer to the event source evSrc.addObserver( respHandler ); // starts the event thread Thread thread = new Thread(evSrc); thread.start(); } }
[편집] C#
using System; // 먼저 이벤트 발생에 사용할 델리게이트 형식을 선언한다. // 이것은 System.EventHandler 형식과 같은 델리게이트이다. // 이 델리게이트는 추상 옵저버로서의 기능을 제공한다. // 어떠한 구현도 제공하지 않으며, 단지 규약만 제공한다. public delegate void EventHandler(object sender, EventArgs e); // 다음으로, 공개된 이벤트를 선언한다. 이것은 구체적인 서브젝트로서의 기능을 제공한다. public class Button { // 공개된 이벤트를 선언한다. public event EventHandler Clicked; // 관습적으로, .NET 이벤트 발생은 가상 메서드로 구현된다. // 이는 하위 클래스가 해당 이벤트를 재정의를 통해 사용할 수 있게 하고, 발생 여부도 조정할 수 있게 한다. protected virtual void OnClicked(EventArgs e) { // Clicked 이벤트에 등록된 모든 EventHandler 델리게이트를 호출한다. if (Clicked != null) Clicked(this, e); } } // 이제 옵저버 클래스에서 이벤트에 델리게이트를 등록하거나 등록 해제할 수 있다: public class Window { private Button okButton; public Window() { okButton = new Button(); // Clicked 이벤트에 델리게이트를 등록하는 부분이다. 등록 해제는 -= 연산자를 사용한다. // 다수의 옵저버가 Clicked 이벤트에 델리게이트를 등록하는 것이 가능하다. okButton.Clicked += new EventHandler(okButton_Clicked); } private void okButton_Clicked(object sender, EventArgs e) { // 이 메서드는 Button 클래스에서 Clicked(this, e) 를 호출할 때마다 실행된다. } }