2장 - 옵저버 패턴
- Created
- 2022년 9월 3일 PM 8:28
옵저버 패턴이란?#

신문사 + 구독자 = 옵저버 패턴
- 신문사는 주제(subject), 구독자를 옵저버(observer)라고 부른다.
- 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에 연락이 가고 자동으로 내용이 갱신되는 방식으로 다대다 의존성을 정의합니다.
- 옵저버 패턴은 보통 주제 인터페이스와 옵저버 인터페이스가 들어있는 클래스 디자인으로 구현합니다.
- 옵저버로 등록하거나 탈퇴하고 싶은 경우
Subject인터페이스에 있는 메소드를 사용합니다. - 옵저버가 될 가능성이 있는 객체는
Observer인터페이스를 구현해야합니다.- 주제의 상태가 바뀌었을 때 호출되는 update() 메소드만 구현
- 옵저버에는 데이터를 보내주는 푸시(push) 방식과 옵저버가 데이터를 가져오는 풀(pull) 방식으로 구분할 수 있습니다.
- Push 방식의 경우 매게변수가 변경될 가능성이 있다.
- Pull 방식의 경우 옵저버가 필요한 데이터를 가져올 수있기 때문에 더 느슨하게 결합될 수 있다.
- 대체로 옵저버가 필요한 데이터를 가져오도록 하는게 더 좋은 방법입니다.
느슨한 결합#
- 느슨한 결합은 객체들이 상호작용 할 수는 있지만, 서로를 잘 모르는 관계를 의미합니다.
→ 느슨할 결합을 이용하면 유연성이 아주 좋아짐
- 주제는 옵저버가 특정 인터페이스를 구현한다는 사실만 알고있습니다.
- 새로운 옵저버가 추가될때도 주제는 신경쓸 필요없이
Observer인터페이스만 구현되어 있으면 됩니다. - 주제나 옵저버를 다은 용도로 활용할 일이 있다고 해도 손쉽게 재활용이 가능합니다.
- 서로 느슨하게 결합되어 있으므로 주제나 옵저버 인터페이스를 구현한다는 조건만 만족한다면 어떻게 고쳐도 문제가 생기지 않습니다.
요구사항#
Weather-O-Rama의 차세대 인터넷 기반 기상 스테이션 구축 시스템


- Weather-O-Rama와 계약을 체결하면 WeatherData 객체를 통해 현재 조건, 기상통계, 기상 예보 이 3가지 디스플레이에 갱신하며 보여주어야 함
- 기상 관측이 변경된다면
measurementsChanged()가 호출된다. - 다른 개발자가 새로운 디스플레이를 추가할 수 있고, 이후 화면에 몇가지의 항목들이 더 추가 될 수 있음
해결 방법 1 : 메소드 직접 호출#
첫번째 해결 방법은 아주 단순하게 measurementsChanged() 메소드에 디스플레이 객체의 메소드를 직접호출하는 방법이다.
java
public class WeatherData {
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentConditionsDisplay.update(temp, humidity, pressure);
statisticDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
}단점#
- 디스플레이 객체가 변경될 수도 있다.
- 바뀔 수 있는 부분은 캡슐화 해야한다.
- 구체적인 구현에 맞춰 코딩했으므로 프로그램을 고치지 않고는 다른 디스플레이 항목을 추가할 수 없다.
- 실행 중 디스플레이 객체를 더하거나 뺄수없다.
해결 방법 2 : 옵저버 패턴 적용#
이제 옵저버 패턴을 적용해 느슨한 결합을 만족시키고, 확장성을 지니도록 변경해보겠습니다.
java
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}java
public interface Observer {
// push 방식
void update(float temperature, float humidity, float pressure);
// pull 방식
void update();
}java
public interface DisplayElement {
void display();
}java
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
this.observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(observers.indexOf(o));
}
@Override
public void notifyObservers() {
// 실질적인 옵저버의 호출을 담당
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
float getTemperature() {
return temperature;
}
float getHumidity() {
return humidity;
}
float getPressure() {
return pressure;
}
void measurementsChanged(){
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
...
}java
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;
public CurrentConditionsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
// 푸시 방식을 경우
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
// 풀 방식을 경우
@Override
public void update() {
this.temperature = weatherData.getTemperature();
this.humidity = WeatherData.getHumidity();
display();
}
@Override
public void display() {
System.out.println("현재 상태: 온도 " + temperature
+ "F, 습도 " + humidity + "%");
}
}장점#
- 객체들간 느슨한 결합을 이뤄내었기 때문에 각자의 책임에만 집중하면 된다.
- 런타임 도중에도 옵저버 객체를 추가하거나, 삭제할 수 있다.
- 옵저버 객체도 주제 인터페이스를 구현한다면 주제이자 옵저버가 될 수 있다.
정리#
- 옵저버 패턴은 객체들 사이에 일대다 관계를 정의합니다.
- 옵저버 패턴을 사용하면 주제가 데이터를 보내거나 옵저버가 데이터를 가져올 수 있지만 일반적으로 데이터를 가져오는게 더 옳은 방식입니다.
- 옵저버 패턴은 자주 사용되는 패턴으로 MVC를 배울때도 다시 거론됩니다.