ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 옵저버 패턴
    Programming/Design Patterns 2010. 5. 3. 08:11

     

    옵저버 패턴의 정의

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

    • 어떤 객체의 상태를 느슨하게 결합된 다른 객체들한테 전달하기 위한 패턴이다.

    *한 객체의(주제) 상태가 바뀌면 그 객체에 의존하는 다른객체들(옵저버)에게 연락이 간다.
    *주제 이면서 옵저버 일수도 있다.(책에서는 출판사(주제)와 구독자(옵저버)를 예로 들었는데..구독자 이면서 동시에 출판사 일수도 있다.)

    • 신문 구독 메커니즘만 제대로 이해할 수 있다면 옵저버 패턴을 쉽게 이해할 수 있다.
    • 옵저버 패턴(Observer Pattern)에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고

           자동으로 내용이 갱신되는 방식으로 일대다(one-to-many)의존성을 정의합니다

    1. 옵저버 패턴의 조건.

    1) 옵저버 패턴에서 상태를 저장하고 있는 것은 주제 책체다. 옵저버는 이 상태를 사용은 하지만 반드시 가지고 있어야 하는갓은 아니다. 되도록이면 서로간에 결합도는 떨어뜨리고 상태를 통하여 옵저버가 주제에 의존적인 성질을 가지게 한다.

    2) 첫째도 느슨한 결합 두째도 느슨한 결합. 아주 일반적인 상식이다.
    객체간의 결합도가 높아질수록 수정 유지보수는 아주 힘든 작업이 된다(스트레티지 패턴에서 상속 보다는 위임을 쓰면서 메소드만 가진 객체를 분리 하였듯이(인터페이스)). 그래서 MVC 패턴도 그렇지만(이건 디자인 차원이 아니라 아키텍쳐 수준의 패턴이지만) 일단 객체간의 결합도는 낮추는게 일반론이다.
    책에서 설명하는 느슨한 결합의 장점을 뽑으라면 일단.

    -옵저버를 언제든지 추가할수 있다. ( )
    -새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경할 필요없다()
    -주제와 옵저버를 서로 독립적으로 재사용할수 있다.
    -주제나 옵저버가 바뀌더라도 서로에게 영향을 미치지 않는다.()

    위 예제는 여러개로 나누어 했지만 결국 지문 자체에서 전달하고자 하는 내용은 서로 연결되어 있다. 요약하자면
    결합도를 낮추어라.(확장하기 용이 하게 수정은 최대한 간단히)

    3)디자인의 원칙

    서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야한다.

    느슨하게 결합하는 디자인을 사용하면 변경사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있습니다.

    객체 사이의 상호의존성을 최소화 할수 있기 때문이죠.

     

     

     

    2. 패턴이 적용된 예제.

    우선 패턴을 적용할 예제에 대해서 알아보자.
    기상모니터링 애플리케이션을 구현 하려 한다. 이 시스템은 다음과 같이 3개의 요소로 이루어져 있고(기상스테이션, WeatherData, 디스플레이 장비)
    우리는 WeatherData 를 통해서 여러개(3개 혹은 그이상의 추가 디스플레이장비)의 디스플레이 장비에 데이터를 보여 주게 된다.

     

     

    위 가상 스테이션 애플리케이션을 옵저버 패턴으로 구현 하기 위해 분석을 하여 보자.
    1. 옵저버 패턴의 정의를 떠올리면 one to many 관계 이므로 one = WeatherData 이고 many = Display장치 가된다는걸 알수있다.
    2. WeatherData 객체를 주제 객체로 하고 디스플레이 장치를 옵저버로 하는경우 디스플레이장치 에서 자기가 원하는 정보를 얻기 위해 WeatherData객체에 등록을 할수 있는 메소드가 필요한다.
    3. 모든 디스플레이 장치는 항목이 다를수 있다. 그러므로 바로 이부분이 공통 인터페이스를 이용해서 처리가 가능 하다. 각 장치마다 구성요서(어떤 장치는 온도 습도 어떤 장치는 날씨)의 형식이 달라도
    똑같은 인터페이스를 구현해야만 WeatherData객체에서 가상스테이션에서 언더온 측정값을 보낼수 있다.
    4. 즉 모든 디스플레이 장치는 WeatherData에서 호출할수 있는 update()메소드가 필요하다. 그리고 이 update()메소드는 모든 장치들이 구현하는 공통인터페이스에서 정의 되야 한다.

    2.1 가상 스테이션 애플리케이션의 사용자 구현 옵저버 패턴

    위의 분석 결과를 적용한 UML을 확인해 보고 코드를 작성 하자.

      public interface Subject {
       public void registerObserver(Observer o);
      public void removeObserver(Observer o);
      public void notifyObservers();
      }
      
      public interface Observer {
      public void update(float temp, float humidity, float pressure);
      }
      public interface DisplayElement {
      public void display();
      }
      import java.util.*;
      public class WeatherData implements Subject {
      private ArrayList observers;
      ' ' '
      public WeatherData() {
      observers = new ArrayList();
      }
      @SuppressWarnings("unchecked")
      public void registerObserver(Observer o) {
      observers.add(o);
      }
      public void removeObserver(Observer o) {
      int i = observers.indexOf(o);
      if (i >= 0) {
      observers.remove(i);
      }
      }
      public void notifyObservers() {
      for (int i = 0; i < observers.size(); i++) {
      Observer observer = (Observer)observers.get(i);
      observer.update(temperature, humidity, pressure);
      }
      }
      public void notifyObservers(List observerList) {
      Iterator iter=observerList.iterator();
      while(iter.hasNext()){
      Observer observer = (Observer)iter.next();
      observer.update(temperature, humidity, pressure);
      }
      }
      public void measurementsChanged() {
      List observerList = observers.subList(0, 2);
      notifyObservers();
      // notifyObservers(observerList); 
      }
      public void setMeasurements(float temperature, float humidity, float pressure) {
      this.temperature = temperature;
      this.humidity = humidity;
      this.pressure = pressure;
      measurementsChanged();
      }
      // other WeatherData methods here  ' ' '
      }

     

      
      public class CurrentConditionsDisplay implements Observer, DisplayElement {
      private float temperature;
      private float humidity;
      private Subject weatherData;
      public CurrentConditionsDisplay(Subject weatherData) {
      this.weatherData = weatherData;
      weatherData.registerObserver(this);
      }
      public void update(float temperature, float humidity, float pressure) {
      this.temperature = temperature;
      this.humidity = humidity;
      display();
      }
      public void display() {
      ...
      }
      }
      public class WeatherStation {
      public static void main(String[] args) {
      WeatherData weatherData = new WeatherData();
      CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
      StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
      ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
      weatherData.setMeasurements(80, 65, 30.4f);
      weatherData.setMeasurements(82, 70, 29.2f);
      weatherData.setMeasurements(78, 90, 29.2f);
      }
      }

    체감온도는 기온을 T라고 하고 상대 습도를 RH라고 했을 때, 다음과 같은 식으로 계산된다고 합니다.

    private float computeHeatIndex(float t, float rh) {
    float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) +
    (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) +
    (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
    (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 *
    (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) +
    (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
    0.000000000843296 * (t * t * rh * rh * rh)) -
    (0.0000000000481975 * (t * t * t * rh * rh * rh)));
    return index;
    }

     2.2 가상 스테이션 애플리케이션의 자바내장 옵저버 패턴
    위와 동일한 역활을 하는 프로그램을 이번에는 자바의 내장된 옵저버패턴을 사용해서 구현해 보자.

    public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() { }

    public void measurementsChanged() {
    setChanged();
    notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure = pressure;
    measurementsChanged();
    }

    public float getTemperature() {...}
    public float getHumidity() {...}
    public float getPressure() {

    return pressure;

    }

    }

    public class CurrentConditionsDisplay implements Observer {
    Observable observable;
    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable) {
    this.observable = observable;
    observable.addObserver(this);
    }

    public void update(Observable obs, Object arg) {
    if (obs instanceof WeatherData) {
    WeatherData weatherData = (WeatherData)obs;
    this.temperature = weatherData.getTemperature();
    this.humidity = weatherData.getHumidity();
    display();
    }
    }

    public void display() {...}
    }


    자바의 내장된 옵저버패턴을 이용하므로서 얻어지는 장점과 단점

    장점
    1.Subject(주제) 쪽에서 일방적으로 데이터를 보내는 (푸시방식)방식 많이 아니라 Observer(옵저버)쪽에서 데이터를 가져가는 (풀방식)방식을 선택해서 사용할수 있다.

    • 풀 방식의 처리가 좀더 권장되어진다. 이유는 수많은 옵저버가 필요로 하는걸 모두 파악하기란 자원의 낭비가 심하기 때문이다. 풀방식으로 처리된다면 등록된 옵저버중에 필요한 내용만 가져갈수도 있고, 만약 주제가 확장되어 상태가 몇개가 추가된다고 하더라도 옵저버에 갱신된 상태를 전달하기 위한 메소드를 일일히 고칠 필요 없이 게터 메소드 하나만 추가 하고 필요한 옵저버가 필요한 상태만 가지기 처리 할수 있기 때문이다.

    2.setChanged 메소드를 통하여 옵저버 메소드들을 갱신 하는 방법에 있어 좀더 유연성을 가질수 있다.(온도가 변경될때 1도 이상 변경될때만 디스플레이 장치에 값이 전달되야 할경우)

    3.객체 생성자에 관련 옵저버들을 처리 하기 위한 자료 구조를(스택, 큐 구조 또는 arrayList를 이용한 데이터 구조) 만들 필요가 없다.

    • 일단 스택 큐 언급은 했으나 옵저버 패턴을 이용한 처리에서 연락이 가는 순서에 의존한 처리는 잘못된 처리 방식 이므로 굳이 스택 이나 큐같은 방식이 아니라 set 이나 리스트 혹은 map 과같이 어떤 자료구조를 쓰더라도 상관은 없겠다. 단 순서에 의존한 처리만 하지 않는다면 말이다.

    일단 위에 기술한 모든 내용은 사용자가 직접 옵저버 패탠을 구현하여 처리 해도 얼마든지 구현이 가능 하지만 자바 내에서 제공해주는 api를 이용하여 좀더 편해진다는게 사용 이유라면 이유다.

    단점
    1. 일단 가장치명적인 단점은 Observable은 인터페이스가 아닌 클래스 라서 서브 클래스를 만들어야 하고
    2. 다른 수퍼 클래스를 상속받아 확장하고 있는경우 Observable의 기능을 추가 할수 없다. 그러므로 재사용성이 급격이 제약된다.(패턴의 사용이유는 재사용성의 증가인데..목적을 상실했다.)
    3. setChanged 메소드가 protected로 선언되어 Observable 클래스를 상속받은 서브 클래스에서만 호출이 가능 하다. 이 이야기는 인스턴스 변수로 사용할수 없다는 이야기다.
    (인스턴스 변수가 먼지 모른다고 하면 ... OTL 이다 스트레티지 패턴에 잘 나와 있으니 먼지 찾아 보길 바란다.) 결국 이러한 디자인은 상속보다는 구성을 사용한다는 근본 원칙에도 위배 된다.

    그렇지만 이러한 단점을 가지었음에도 Observable을 확장한 클래스를 쓸수 있는 상활이라면 쓰면 좋을 거다.

    객체지향의 원칙

    • 바뀌는 부분은 캡슐화한다.
    • 상속보다는 구성을 활용한다.
    • 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.
    • 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

    이번장 요약

    - 옵저버 패턴에서는 객체들 사이에 일대다 관계를 정의합니다.

    - 주제, 또는 Observable 객체는 동일한 인터페이스를 써서 옵저버에 연락을 합니다

    - Observable에서는 옵저버들이 Observer 인터페이스를 구현한다는 것을 제외하면 옵저버에 대해 전혀 모르기 때문에이들 사이의 결합은 느슨한 결합입니다.

    - 옵저버 패턴을 이용하면 주제객체에서 데이터를 보내거나(푸시방식) 옵저버가 데이터를 가져오는 방식(풀방식)을 쓸수 있습니다. (풀방식이 더 옳은 것으로 간주됩니다.)

    - 옵저버들한테 연락을 돌리는 순서에 절대로 의존하면 안 됩니다.

    - 자바에는 범용으로 쓸 수 있는 java.util.Observable를 비롯하여 옵저버 패턴을 구현한 것들이 여럿 있습니다.

    - java.util.Observable의 몇가지 문제접에 주의합시다.

    - 스윙 및 여러 GUI 프레임워크에서 옵저버 패턴이 많이 쓰입니다.

    옵저버 패턴은 JavaBeans 나 RMI를 비롯하여, GUI가 아닌 다른 부분에서도 광범위하게 쓰입니다.

    % 자바 RMI 란?

    서로 다른 런타임 환경(원격지)에 존재하는 자바 객체를 마치 자신의 런타임 환경에 존재하는 객에와 다름없이 사용할 수 있도록

    분산된 어플리케이션 객체간 저 수준의 네트워크 코드를 추상화 함으로써 개발자가 네트워크에 분산된 자바 객체를 투명하게 접근할 수 있도록

    정의한 자바의 분산 객체 기술..

    'Programming > Design Patterns' 카테고리의 다른 글

    디자인 패턴 요약 정리  (0) 2010.05.03
    팩토리 패턴  (0) 2010.05.03
    스트래티지 패턴  (0) 2010.05.03
    데코레이터 패턴  (0) 2010.05.03
Designed by Tistory.