옵저버 패턴(Observer Pattern)에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고, 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.
예시로 기억하자
WeatherData 객체는 온도, 습도, 기압 정보를 가지고 있으며, 해당 정보가 변경될 때마다 다음의 세 객체에 데이터가 갱신되어야 한다. 1)현재 날씨 2) 날씨 예보 3) 기상 통계
class WeatherData {
getTemperature()
getHumidity()
getPressure()
measurementsChanged() // 기상 관측값이 갱신되면 해당 메소드가 호출됨
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentWeatherDisplay.update(temp, humidity, pressure);
forecastWeatherDisplay.update(temp, humidity, pressure);
staticsticDisplay.update(temp, humidity, pressure);
}
}
문제점 : 다른 화면을 추가하고자 한다면 measurementsChanged() 메소드를 고쳐야 함
해결책 : 바뀔 수 있는 부분을 encapsulate 해야 함
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
class WeatherData implements Subject {
registerObserver()
removeObserver()
notifyObserver()
getTemperature()
getHumidity()
getPressure()
measurementsChanged()
}
구체적인 code
public class WeatherData implements Subject {
private List<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) {
observers.remove(o);
}
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 temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
새로 갱신된 subject 데이터를 전달하는 interface를 정의해야 함
interface Observer {
update() // 새로 갱신된 주제 데이터를 전달하는 인터페이스
}
이 예시의 경우, 화면에 표시하는 기능이 공통되므로 이를 interface로 정의한다.
interface DisplayElement {
display() // 화면에 표현시키는 인터페이스
}
class CurrentWeather implements Observer, DisplayElement{
update()
display() { // 현재 측정 값을 화면에 표시 }
}
class ForcastWeather implements Observer, DisplayElement{
update()
display() { // 날씨 예보 표시 }
}
class StatisticsDisplay implements Observer, DisplayElement{
update()
display() { // 평균 기온, 평균 습도 등 표시 }
}
하나만 구체적인 코드를 보자면,
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
private WeatherData weatherData;
public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
DiscomfortDisplay를 추가한다면?class DiscomfortDisplay implements Observer, DisplayElement{
update()
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);
weatherData.removeObserver(forecastDisplay);
weatherData.setMeasurements(62, 90, 28.1f);
}
}
private Subject_impl s;
public Observer_impl(Subject_impl ss) {
this.s = ss;
s.registerObserver(this)
}
이때 Subject의 정보를 받아올 수 있도록 한다.
Subject_impl에 정의된 execute() (* notifyObserver와 정보 전달 기능을 하는 메서드)에 쓰인다.
방법1. Subject가 변경된 데이터를 update 메서드의 인자로 직접 전달 (위 예시)
public interface Observer {
void update(int newNumber);
}
public class DigitObserver implements Observer {
@Override
public void update(int newNumber) {
System.out.println("DigitObserver: " + newNumber);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
방법2. Observer_impl이 Subject_impl에 대한 참조를 갖도록 하기
public interface Observer {
void update(NumberGenerator generator);
}
public class DigitObserver implements Observer {
@Override
public void update(NumberGenerator generator) {
System.out.println("DigitObserver: " + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
전달할 정보 정의
위 예시의 경우 temperature, humidity, pressure
Observer인터페이스에 정의된 update()메서드를 모든 observer에 대해 실행시키는 notifyObservers()
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
notifyObservers를 실행시키는 메소드를 만든다.