옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 갱신되는 방식으로 일대다(one to many) 의존성을 정의합니다.
옵저버 패턴이란?
이 글을 보시는 독자 여러분의 이해를 돕기 위해 신문과 구독자라는 예시를 들어 설명하겠습니다.
신문을 Subject로 생각하고 구독자를 Observer로 생각하시면 편합니다.
신문은 매일 정보가 바뀌며 구독자들은 바뀐 정보를 자동적으로 받습니다.
이처럼 옵저버 패턴은 Subject(신문)이라는 객체의 정보가 바뀌면 Observer(구독자)에게 연락이 가고 자동적으로 정보가 갱신되는 패턴입니다.
옵저버 패턴을 사용하는 경우 / 사용해야 하는 경우
책에서는 기상 모니터링 애플리케이션 도메인을 설명하면서 옵저버 패턴을 설명하였습니다.
도메인에 대해 간단히 설명하자면 Weather Station에서 다양한 센서를 이용해 정보를 받고 Weather Data Object에게 전달을 합니다. Weather Data Object(Subject)에서 Display Device(Object)에게 정보를 전달해줍니다.
1번
- registerObserver(), removeObserver()은 Observer을 등록 / 제거하는 메소드 입니다.
- notifyObservers() 메소드는 Observer들에게 상태 변화를 알려주는 메소드 입니다.
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
2번
- 1번의 notifyObserver()메소드에서 update를 이용해 Observer에게 상태 변화를 알려줍니다.
public interface Observer {
void update(float temperature, float humidity, float pressure);
}
3번
- Subject interface를 implements 하는 구현 클래스입니다.
4번
- Observer interface를 implements 하는 구현 클래스입니다.
뭔가 이해는 될 것 같은데.. 자세한 코드를 보면서 이해를 하고 싶어
이런 생각을 하신 여러분은 "개발자"입니다.
자세한 코드
import java.util.ArrayList;
import java.util.List;
public class WeatherData implements Subject {
private float temperature;
private float humidity;
private float pressure;
private List<Observer> observers;
public WeatherData() {
observers = new ArrayList<Observer>();
//observer을 저장하는 리스트를 생성한 뒤, 생성자에서 초기화 함
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
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 this.temperature;
}
public float getHumidity() {
return this.humidity;
}
public float getPressure() {
return this.pressure;
}
}
Subject를 구현한 클래스입니다.
Observer을 관리하는 리스트를 인스턴스 변수로 생성한 뒤, 생성자에서 초기화하고 있습니다.
그 후, measurementsChanged라는 메서드를 통해 갱신된 측정값을 받으면 notifyObservers를 통해 observer들에게 연락을 한 뒤 정보 갱신을 합니다.
import ObserverPattern.DisplayElement;
import ObserverPattern.Observer;
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 display() {
System.out.println("현재 상태: 온도 "+temperature+"F, 습도 "+humidity+"%");
}
}
Observer, DisplayElement를 구현한 클래스입니다.
생성자의 매개변수로 WeatherData를 받은 뒤, weatherData 변수 값에 할당한 뒤, observer로 본인(this)을 추가했습니다.
온도와 습도를 표시하기 때문에 temperature, humidity를 인스턴스 변수로 생성하였습니다.
WeatherData의 notifyObservers 메서드에서 사용하는 update 메서드를 구현하였습니다.
정보를 갱신한 뒤, display메서드를 호출해 정보를 출력하고 있습니다.
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay =
new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
출력값
현재 상태: 온도 80.0F, 습도 65.0%
현재 상태: 온도 82.0F, 습도 70.0%
현재 상태: 온도 78.0F, 습도 90.0%
코드 개선하기
푸시를 풀로 바꾸자
만약 풍속이나 강수량과 같은 데이터를 추가로 받은 뒤, update 한다고 가정해 보자.
@Override
public void update(float temperature, float humidity, float pressure, float precipitation) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
CurrentDisplay의 update 메소드는 이렇게 변할 것이다.
이때 "뭐가 문제야?"라고 하는 사람과 "흠.. 이게 문제네"라고 하는 사람이 있을 것이다.
바로 불필요한 데이터를 매개변수로 계속 받고 있다는 점이다.
그러므로 필요한 데이터를 골라서 가져가도록 만드는 방법이 좋은 방법이다.
public void notifyObservers() {
for(Observer observer: observers) {
observer.update();
}
}
WeatherData의 notifyObservers 메소드에서 인자 없이 호출하도록 변경을 해야 한다.
public interface Observer {
public void update();
}
Observer의 update 또한 매개변수가 없도록 변경을 해야 한다.
public void update() {
this.temperature = weatherDate.getTemperature();
this.humidity = weather.getHumidity();
display();
}
마지막으로 Observer의 update를 구현한 클래스에서 get 메소드를 통해 정보를 가져오면 된다.