반응형
(본 강의 노트는 한빛 미디어의 <Head First Design Patterns>책을 기반으로 하고 있습니다)
02 옵저버 패턴
옵저버 패턴이란?
목적
- 객체간 일대다 의존 관계를 정의함
- 한 개 객체 상태가 변화될 때, 그 객체와 의존 관계에 있는 모든 객체들이 자동으로 알림을 받고 상태를 갱신
옵저버 패턴은 일종의 푸쉬 서비스를 구현
- 뉴스
- 뉴스 사이트를 방문해서 매번 새로운 뉴스가 있는지 확인하는 것은 번거로움
- 구독 서비스를 신청 시, 새로운 뉴스가 있으면 알려주는 것이 편리함
- 호텔의 모닝콜 서비스
- 일어날 시간을 확인하기 위해 자다 깨다를 반복하는 것은 어려움
- 모닝콜을 원하는 시간에 받거나 알람을 맞추는 것이 바람직함
- 전화번호 프로그램
- 상황: 정보를 확인하기 위해 마우스로 화면의 버튼을 클릭하는 경우
- 버튼이 클릭되었는지 프로그램에서 계속 확인하는 것은 시간 낭비
- 버튼 클릭 이벤트 발생 시, 프로그램에 해당 이벤트를 전달하고, 처리하도록 하는 것이 효율적
- 옵저버 패턴은 푸쉬를 사용하며 절차는 아래와 같음
- 푸쉬를 받고자 하는 사용자가 등록
- 특정 상황이 발생하면, 등록된 사용자에게 모두 알리고 자동으로 데이터가 갱신됨
- 뉴스레터 + 구독자 = 옵저버 패턴
- 뉴스레터 발행자는 Subject(or Publisher)
- 구독자는 Observer(or Subscriber)
디자인 패턴 요소
요소 |
설명 |
이름 |
옵저버(Observer) |
문제 |
1:n 관계에서의 정보 갱신 |
해결방안 |
사용자를 등록하고, 정보가 변동하는 경우 알려주고 값을 자동으로 갱신 |
결과 |
느슨한 커플링(loose coupling), 확장성 |
옵저버 패턴의 클래스 다이어그램
사례1 - Weather( HFDP Ch.2 )
- 날씨
: Weather-O-Rama사의 차세대 인터넷 기반 기상 정보 스테이션 구축
- 시스템 구성
- 기상 스테이션 : 기상 정보를 수집하는 장비
- Weather Data 객체 : 기상 스테이션으로부터 오는 데이터를 추적하는 객체
- 디스플레이 : 사용자에게 현재 기상 조건을 보여주는 장치
- 현재 조건(온도, 습도, 압력), 기상 통계, 간단한 기상 예보를 다른 화면에 표시 가능
-
정보 공급자
: 정기적으로 계속 온도, 습도, 기압을 측정 수집함 -
WeatherData 클래스
- 기상 측정 값이 바뀔 때마다 measurementsChanged() 함수가 호출된다고 함
: 이 함수를 구현해서 디스플레이에 정보를 표시하도록 해야 함
-
코드
public void measuremetsChanged(){ //이미 구현된 함수를 통해 최신 측정값 가져옴 float tmp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); //디스플레이 갱신 currentConditionsDiplay.update(tmp,humidity,pressure); statisticsDiplay.update(tmp, humidity, pressure); }
-
문제점
- measurementsChanged() 함수 내부에서 구체적인 객체를 사용하고 있음
- 이 경우 새로운 디스플레이 화면 추가 혹은 기존 화면을 제거하려면 함수를 수정해야만 함
- 구체적인 객체를 사용하는 부분이 바뀔 수 있는 부분이므로 캡슐화가 필요함
느슨한 결합
- 두 객체가 느슨하게 결합되어 있다
- 그 둘이 상호작용을 하나, 서로에 대해 잘 모르는 경우
- 옵저버 패턴에서는 Subject, Observer 간 느슨한 결합이 만들어짐
- Subject가 Observer 에 대해서 아는 것은 특정 인터페이스를 구현한다는 것이다
- 새로운 Observer는 쉽게 추가하거나 제거 가능
- Observer가 새로 생겨도 Subject는 바뀌지 않음
- Subject, Observer는 독립적으로 재사용이 가능
Weather 클래스 다이어그램
일반 ArrayList 버전
-
Subject.java
package headfirst.observer.weather; public interface Subject{ public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
-
Observer.java
package headfirst.obsever.weather; public interface Observer{ public void update(float tmp, float humidity, float pressure); }
-
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 사용
- 주의 : Deprecated(since JAVA 9) , 자바 9 이후 버전부터는 쓰는걸 권장하지 않음!
- 객체가 Observer가 되는 방법
- Observer 인터페이스 구현 후 Observable의 addObserver()함수 호출
- Observable에서 알림을 주는 방법
- setChanged() 메소드를 호출해서 객체의 상태가 바뀌었음을 알림
- notifyObservers() 또는 notifyObservers(Object arg) 함수를 호출해서 알림
- 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");
}
}
클래스 다이어그램
-
문제점은?
- java.util.Observable이 인터페이스가 아니라 클래스로 되어 있음
- 다른 클래스로부터 상속 받아야 하는 클래스는 Observable에서부터 상속 받을 수 없음
- setChanged( )함수가 protected로 되어 있음
- Observable에서 상속 받아야 사용할 수 있으므로 문제가 되지는 않음
- 단, 상속보다는 구성을 사용한다는 디자인 원칙에 위배
- 자바 9부터 deprecated됨
사례2 - Swing의 ActionListener
- Swing의 JButton은 Observable(Subject)
- JButton의 부모 클래스인 AbstractButton에는 addActionListener()함수가 존재함
- Swing의 이벤트 리스너는 Observer에 해당됨
- JButton에 이벤트 발생 시, JButton에 등록되어 있는 리스너의 actionPerformed() 함수를 호출
Swing의 ActionListener 클래스다이어그램
옵저버 패턴
설계
- 인터페이스 분리(ISP : 인터페이스 분리 원칙)
- 구테적인 클래스 상속
- 클라이언트는 구체 클래스보다는 추상 클래스 사용 후 상속
- 정보 제공자(subject)
- 상태가 변경되면 알림 기능(notify)
- 알림 대상이 되는 옵저버를 사전 등록(register)
- Observable 또는 Publisher라고도 함
- 정보 수신자(Observer)
- 정보 제공자의 상태가 변경되면 그 내용을 받아서 반영함(update)
- Subscriber라고도 함
클래스 다이어그램
반응형
'Computer Science > 디자인패턴' 카테고리의 다른 글
[Head First Design Patterns] 04 팩토리 패턴 (0) | 2020.10.20 |
---|---|
[Head First Design Patterns] 01 디자인 패턴 소개 (0) | 2020.10.18 |
Introduction Advanced OOP (0) | 2020.10.18 |
[Head First Design Patterns] 03 데코레이터 패턴 (0) | 2020.10.07 |
SOLID 원칙 (0) | 2020.09.12 |