옵서버 패턴
옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.
구현
편집이 패턴의 핵심은 옵저버 또는 리스너(listener)라 불리는 하나 이상의 객체를 관찰 대상이 되는 객체에 등록시킨다. 그리고 각각의 옵저버들은 관찰 대상인 객체가 발생시키는 이벤트를 받아 처리한다.
UML 다이어그램으로는 아래처럼 표현된다. 관찰 대상인 객체는 “이벤트를 발생시키는 주체”라는 의미에서 Subject로 표시되어 있다.
이벤트가 발생하면 각 옵저버는 콜백(callback)[1]을 받는다. notify
함수는 관찰 대상이 발행한 메시지 이외에, 옵서버 자신이 생성한 인자값을 전달할 수도 있다.
각각의 파생 옵서버는 notify
함수를 구현함으로써 이벤트가 발생했을 때 처리할 각자의 동작을 정의해야 한다.
주체에는 일반적으로 등록(register), 제거(unregister) 메서드가 있는데, 전자는 새로운 옵저버를 목록에 등록하고 후자는 목록에서 옵저버를 뺀다. 등록과 제거 메서드 이외에도, 임시로 작동을 멈추거나 재개하는 메서드를 이용해 이벤트가 계속해서 있을 때 홍수같이 발생하는 요청을 제어할 수도 있다.
옵서버 패턴이 많이 쓰인 시스템에서는 순환 실행을 막는 메카니즘이 필요하다. 이벤트 X가 발생하면 옵저버A가 옵저버B를 갱신한다고 가정해보자. 그런데 옵저버B가 이 처리를 위해 옵저버A를 갱신한다면, 이는 다시 A로 하여금 이벤트 X를 발생하게 한다. 이같은 상황을 막기 위해 이벤트 X가 한번 처리된 후에는 A가 이벤트 X를 다시 발생시키지 않는 방법이 요구된다.
대표적인 사례
편집옵서버 패턴의 대표적인 이용 사례는 다음과 같다.
- 외부에서 발생한 이벤트(사용자 입력같은)에 대한 응답. 이벤트 기반 프로그래밍을 참조하라.
- 객체의 속성 값 변화에 따른 응답. 종종 콜백은 속성 값 변화를 처리하기 위해 호출될 뿐 아니라 속성 값 또한 바꾼다. 때때로 이벤트 연쇄의 원인이 될 수 있다.
옵서버 패턴은 모델-뷰-컨트롤러(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 >");
// 이벤트 발행 주체를 생성함 - stdin으로부터 문자열을 입력받음
final EventSource evSrc = new EventSource();
// 옵저버를 생성함
final ResponseHandler respHandler = new ResponseHandler();
// 옵저버가 발행 주체가 발행하는 이벤트를 구독하게 함
evSrc.addObserver( respHandler );
// 이벤트를 발행시키는 쓰레드 시작
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)를 호출할 때마다 실행된다.
}
}