옵저버 패턴

위키백과, 우리 모두의 백과사전.
이동: 둘러보기, 찾기

옵저버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.

목차

[편집] 구현

이 패턴의 핵심은 옵저버 또는 리스너(listener)라 불리는 하나 이상의 객체를 등록하거나 자신을 등록시킨다. 그리고 관찰되는 객체(또는 주제)에서 발생하는 이벤트를 전달한다.

UML 다이어그램으로는 아래처럼 표현된다.

Observer.svg

이벤트가 발생하면 각 옵저버는 콜백(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) 를 호출할 때마다 실행된다.
    }
}

[편집] 바깥 고리

개인 도구
이름공간

변수
행위
둘러보기
인쇄/내보내기
도구모음
다른 언어