프로그램 아키텍쳐/디자인패턴

옵저버 패턴 ( Observer pattern )

본클라쓰 2009. 1. 3. 16:07

 옵저버 패턴(Onserver Pattern)은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(One-to-Many) 의존성을 정의한다. 보통 옵저버 패턴이 사용되는 기능에는 정보 갱신이 실시간 이루어 질 필요가 있는 곳에서 사용한다.

 

 옵저버 패턴에서 주제(Subject)는 데이터가 바뀌면 새로운 데이터 값을 옵저버들에게 보내 주는 객체를 지칭한다. 옵저버(Observer)는 주제 객체를 주시하고 있다가 주제의 데이터가 변경되면 갱신 내용을 받는 객체를 말한다. 

 

 옵저버 패턴을 구현하는 방법에는 여러가지가 있지만, 대부분 주제 인터페이스와 옵저버 인터페이스가 들어있는 클래스 디자인을 바탕으로 한다. 주제객체에는 옵저버를 등록, 삭제할 수 있는 메소드데이터가 변하면 옵저버들에게 데이터를 보내는 메소드가 필요하다. 옵저버 객체에는 주제객체가 데이터를 갱신 할 때 사용하는 메소드를 인터페이스 상속을 통해 구현하면 된다. 이 때 주제는 모든 옵저버를 지배한다. 옵저버는 주제에게 의존성을 가지고 기다리는 입장이다. 이런 방법을 사용하면 여러 객체에서 동일한 데이터를 제어하도록 하는 것에 비해 더 깔끔한 객체지향 디자인을 만들 수 있다.

 

 하지만 주제가 옵저버에 대해 아는 것은 옵저버가 특정 인터페이스를 구현한다는 것 뿐이고, 옵저버가 무엇을 하는지에 대해서는 알지 못한다. 또 주제객체에서 새로운 옵저버를 언제든지 추가 할 수 있다. 주제객체는 observser인터페이스를 구현하는 객체의 목록에만 의존하기 때문에 언제든지 새로운 옵저버를 추가할 수 있는 것이다

 

 새로운 형식의 옵저버를 추가하려고 할 때도 주제를 변경할 필요가 없다. 옵저버가 되어야 할 새로운 클래스가 있다 해도 주제를 바뀌야 할 필요는 없다. 새로운 클래스에 observer인터페이스를 구현하고 옵저버를 등록하기만 하면 된다. 그래서 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.

 

 또한, 주제나 옵저버가 바뀌더라도 서로에게 영향을 미치지 않는다. 이런 관계를 '느슨한 결합(Loose Coupling)의 위력' 이라고 한다. 이 말은 그 둘이 상호작용을 하지만 서로에 대해 서로 잘 모른다는 것을 의미한다. 옵저버 패턴을 이용하면 주제객체에서 데이터를 보내거나(푸시방식) 옵저버가 데이터를 가져오는(풀 방식)을 쓸 수 있지만 두가지 방식중에 풀 방식을 더 옳은 것으로 간주한다.

 

 

[ 옵저버패턴을 정리하여 간단하게 프로그래밍한 소스코드 ]

 

주제객체 인터페이스를 정의 하는 코드 

/** 주제객체 최상의 인터페이스 (옵저버 등록, 제거, 옵저버 갱신을 위한 메소드 정의) */
public interface Subject {


    public void registerObserver(Observer o);    /* 옵저버 등록 메소드*/
    public void removeObserver(Observer o);    /* 옵저버 제거 */
    public void notifyObservers();    /* 옵저버에 내용 전달 */
}

 

옵저버객체 인터페이스를 정의하는 코드

/** 옵저버객체 최상위 인터페이스(정보 갱신을 위한 메소드 정의) */
public interface Observer {


    public void update(int I_info);    /* 정보 갱신 */


}

 

실제 주제객체를 사용하는 클래스

import java.util.*;

 

/** 주제객체 인터페이스를 사용하는 클래스 */
public class IntData implements Subject 


    private ArrayList<Observer> observers;   /* 옵저버를 등록하고 관리 할 목록배열 */


    private int I_info;    /* 갱신에 사용 할 데이터 */


    /** 주제클래스 생성자(옵저버리스트를 생성)  */
    public IntData() {
        this.observers = new ArrayList<Observer>();
    }
 
    /** 옵저버를 등록 */
    public void registerObserver(Observer o) {


        try {
            observers.add(o);
        } catch( Exception e ) {
            System.out.println("Error");
        }
    }
 
    /** 옵저버 제거 */
    public void removeObserver( Observer o ) {


        int i = observers.indexOf(o);
        if(i >= 0){ observers.remove(o); }
    }

 

     /** 옵저버를 갱신 */
    public void notifyObservers() {


        try {


            for( int i = 0 ; i < observers.size(); i++){
                Observer observer = (Observer)observers.get(i);
                observer.update(I_info);
            }


        }catch( Exception e ) { System.out.println("Error");  }
    }

 

    /** 데이터를 설정(변경시) */
    public void setMesurements(int info) {


        this.I_info = info;
        notifyObservers();
    }
}

 

실제 옵저버객체를 사용하는 클래스  

/** 옵저버인터페이스를 구현한 클래스 */
public class DisplayData implements Observer {


    private int I_info;
    privateIntData intData;     /* 주제객체 */
 
    /** 생성자(주제객체의 데이터를 입력받고, 주제객체에 자신의 존재를 알림)  */
    public DisplayData(IntData intData) {


        try {


            this.intData = intData;
            intData.registerObserver(this);    /* 클래스 생성시 주제 등록 */


        } catch( Exception e ) { System.out.println("Error"); }
    }
 
    /** 업데이트 메소드 */
    public void update( int I_info ) {
        this.I_info = I_info;
        System.out.println("Currnet Data is "+I_info);
    }
}

 

옵저버패턴을 사용한 클래스 테스트 

public class Simulator {


    public static void main( String[] args ) {


        IntData intData = new IntData();
  
        try{
            DisplayData displayData = new DisplayData(intData);
   
            intData.setMesurements(20);
            intData.setMesurements(30);
            intData.setMesurements(40);


        } catch( Exception e ) {
            System.out.println("Error");
        } finally { System.out.println("End"); }
    }
}