Computer Science/디자인패턴

[Head First Design Patterns] 02 옵저버 패턴

계속지나가기 2020. 9. 25. 12:07
반응형

(본 강의 노트는 한빛 미디어의 <Head First Design Patterns>책을 기반으로 하고 있습니다)

02 옵저버 패턴

옵저버 패턴이란?

목적

  • 객체간 일대다 의존 관계를 정의함
  • 한 개 객체 상태가 변화될 때, 그 객체와 의존 관계에 있는 모든 객체들이 자동으로 알림을 받고 상태를 갱신

옵저버 패턴은 일종의 푸쉬 서비스를 구현

  1. 뉴스
  • 뉴스 사이트를 방문해서 매번 새로운 뉴스가 있는지 확인하는 것은 번거로움
  • 구독 서비스를 신청 시, 새로운 뉴스가 있으면 알려주는 것이 편리함
  1. 호텔의 모닝콜 서비스
  • 일어날 시간을 확인하기 위해 자다 깨다를 반복하는 것은 어려움
  • 모닝콜을 원하는 시간에 받거나 알람을 맞추는 것이 바람직함
  1. 전화번호 프로그램
  • 상황: 정보를 확인하기 위해 마우스로 화면의 버튼을 클릭하는 경우
  • 버튼이 클릭되었는지 프로그램에서 계속 확인하는 것은 시간 낭비
  • 버튼 클릭 이벤트 발생 시, 프로그램에 해당 이벤트를 전달하고, 처리하도록 하는 것이 효율적
  1. 옵저버 패턴은 푸쉬를 사용하며 절차는 아래와 같음
  • 푸쉬를 받고자 하는 사용자가 등록
  • 특정 상황이 발생하면, 등록된 사용자에게 모두 알리고 자동으로 데이터가 갱신됨
  1. 뉴스레터 + 구독자 = 옵저버 패턴
  • 뉴스레터 발행자는 Subject(or Publisher)
  • 구독자는 Observer(or Subscriber)

디자인 패턴 요소

요소

설명

이름

옵저버(Observer)

문제

1:n 관계에서의 정보 갱신

해결방안

사용자를 등록하고, 정보가 변동하는 경우 알려주고 값을 자동으로 갱신

결과

느슨한 커플링(loose coupling), 확장성

옵저버 패턴의 클래스 다이어그램

사례1 - Weather( HFDP Ch.2 )

  1. 날씨
    : Weather-O-Rama사의 차세대 인터넷 기반 기상 정보 스테이션 구축
  • 시스템 구성
  1. 기상 스테이션 : 기상 정보를 수집하는 장비
  2. Weather Data 객체 : 기상 스테이션으로부터 오는 데이터를 추적하는 객체
  3. 디스플레이 : 사용자에게 현재 기상 조건을 보여주는 장치
  • 현재 조건(온도, 습도, 압력), 기상 통계, 간단한 기상 예보를 다른 화면에 표시 가능
  1. 정보 공급자
    : 정기적으로 계속 온도, 습도, 기압을 측정 수집함

  2. WeatherData 클래스

    Concrete Subject
  • 기상 측정 값이 바뀔 때마다 measurementsChanged() 함수가 호출된다고 함
    : 이 함수를 구현해서 디스플레이에 정보를 표시하도록 해야 함
  1. 코드

    public void measuremetsChanged(){
      //이미 구현된 함수를 통해 최신 측정값 가져옴
      float tmp = getTemperature();
      float humidity = getHumidity();
      float pressure = getPressure();
      //디스플레이 갱신
      currentConditionsDiplay.update(tmp,humidity,pressure);
      statisticsDiplay.update(tmp, humidity, pressure);
    }
  2. 문제점

  • measurementsChanged() 함수 내부에서 구체적인 객체를 사용하고 있음
    • 이 경우 새로운 디스플레이 화면 추가 혹은 기존 화면을 제거하려면 함수를 수정해야만 함
  • 구체적인 객체를 사용하는 부분이 바뀔 수 있는 부분이므로 캡슐화가 필요함

느슨한 결합

  1. 두 객체가 느슨하게 결합되어 있다
  • 그 둘이 상호작용을 하나, 서로에 대해 잘 모르는 경우
  1. 옵저버 패턴에서는 Subject, Observer 간 느슨한 결합이 만들어짐
  • Subject가 Observer 에 대해서 아는 것은 특정 인터페이스를 구현한다는 것이다
  • 새로운 Observer는 쉽게 추가하거나 제거 가능
  • Observer가 새로 생겨도 Subject는 바뀌지 않음
  • Subject, Observer는 독립적으로 재사용이 가능

Weather 클래스 다이어그램

일반 ArrayList 버전

  1. Subject.java

    package headfirst.observer.weather;
    public interface Subject{
        public void registerObserver(Observer o);
        public void removeObserver(Observer o);
        public void notifyObservers();
    }
  2. Observer.java

    package headfirst.obsever.weather;
    public interface Observer{
        public void update(float tmp, float humidity, float pressure);
    }
  3. WeatherData.java

    package headfirst.observer.weather;
    
    import java.util.*;
    
    public class WeatherData implements Subject {
        private ArrayList observers;
        private float temperature;
        private float humidity;
        private float pressure;
        public WeatherData() {
            observers = new ArrayList();
        }
        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 measurementsChanged() {
            notifyObservers();
        }    
        public void setMeasurements(float temperature, 
                          float humidity, float pressure) {
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            measurementsChanged();
        }     
        public float getTemperature() {
            return temperature;
        }    
        public float getHumidity() {
            return humidity;
        }    
        public float getPressure() {
            return pressure;
        }
    }
    
    
    

제네릭스 버전

package headfirst.observer.weather;

import java.util.*;

public class WeatherData implements Subject {
    private ArrayList<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<Observer>();
    }
    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 = observers.get(i);
        observer.update(temperature, humidity, 
                            pressure);
        }
/*        for (Observer observer : observers) {              
            observer.update(temperature, humidity, 
                            pressure);
        }*/
    }    
    public void measurementsChanged() {
        notifyObservers();
    }    
    public void setMeasurements(float temperature, 
                      float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }            
    public float getTemperature() {
        return temperature;
    }    
    public float getHumidity() {
        return humidity;
    }    
    public float getPressure() {
        return pressure;
    }
}


사례1 - Weather( HFDP Ch.2 ) - Java 버전

  • Java java.util.Observable/java.util.Observer 사용
  1. 주의 : Deprecated(since JAVA 9) , 자바 9 이후 버전부터는 쓰는걸 권장하지 않음!
  2. 객체가 Observer가 되는 방법
  • Observer 인터페이스 구현 후 Observable의 addObserver()함수 호출
  1. Observable에서 알림을 주는 방법
    1. setChanged() 메소드를 호출해서 객체의 상태가 바뀌었음을 알림
    2. notifyObservers() 또는 notifyObservers(Object arg) 함수를 호출해서 알림
  2. Observer가 연락 받는 방법
    • update(Observable o, Object arg)구현

1. WeatherData 코드

package headfirst.observer.weather;

import java.util.*;

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() {
        return temperature;
    }    
    public float getHumidity() {
        return humidity;
    }    
    public float getPressure() {
        return pressure;
    }
}

2. CurrentConditionsDisplay 코드

package headfirst.observer.weather;

public class CurrentConditionsDisplay implements 
                             Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;

    public CurrentConditionsDisplay(
                               Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }
    public void update(Observable o, Object arg) {
        if (arg instanceof WeatherData) {
            WeatherData wd = (WeatherData) arg;
            this.temperature = wd.getTemperature();
            this.humidity = wd.getHumidity();
            display();
        }
    }

    public void display() {
        System.out.println("Current conditions: " 
           + temperature + "F degrees and " + humidity 
           + "% humidity");
    }
}

클래스 다이어그램

  • 문제점은?

    1. java.util.Observable이 인터페이스가 아니라 클래스로 되어 있음
    2. 다른 클래스로부터 상속 받아야 하는 클래스는 Observable에서부터 상속 받을 수 없음
    3. setChanged( )함수가 protected로 되어 있음
    • Observable에서 상속 받아야 사용할 수 있으므로 문제가 되지는 않음
    • 단, 상속보다는 구성을 사용한다는 디자인 원칙에 위배
    1. 자바 9부터 deprecated됨

사례2 - Swing의 ActionListener

  1. Swing의 JButton은 Observable(Subject)
  2. JButton의 부모 클래스인 AbstractButton에는 addActionListener()함수가 존재함
  • Swing의 이벤트 리스너는 Observer에 해당됨
  1. JButton에 이벤트 발생 시, JButton에 등록되어 있는 리스너의 actionPerformed() 함수를 호출

Swing의 ActionListener 클래스다이어그램

옵저버 패턴

설계

  • 인터페이스 분리(ISP : 인터페이스 분리 원칙)
  • 구테적인 클래스 상속
  • 클라이언트는 구체 클래스보다는 추상 클래스 사용 후 상속
  • 정보 제공자(subject)
    • 상태가 변경되면 알림 기능(notify)
    • 알림 대상이 되는 옵저버를 사전 등록(register)
    • Observable 또는 Publisher라고도 함
  • 정보 수신자(Observer)
    • 정보 제공자의 상태가 변경되면 그 내용을 받아서 반영함(update)
    • Subscriber라고도 함

클래스 다이어그램

반응형