옵저버 패턴 이야기로 빠르게 이해하기
1. Ellinia 객체는 주제객체가 쏴주는 int값이 너무 궁금해서 옵저버로 등록시켜달라고 부탁합니다.
2. Ellinia 객체도 공식적인 옵저버가 되었어요 축하해요.
3. 주제 객체의 값이 바뀌어서 주제 객체는 모든 옵저버들에게 int값 9를 보냅니다.
4. Kerning City는 인제는 주제로부터 값을 받아오는게 지루해졌습니다. 나가보겠다고 말하고 옵저버 마을에서 탈퇴합니다.
5. 주제 객체는 Kerning City의 주장을 받아들이고 탈퇴시켜줍니다. 안녕~
6. 주제 객체에 새로운 int값인 12가 들어왔습니다. 옵저버들은 연락을 받고 값을 받아들입니다. Kerning City는 새로운값을 모를겁니다.
옵저버 패턴의 정의
옵저버 패턴은 신문사와 정기 구독자로 이루어지느 신문 구독 서비스에 비유해서 생각하면 됩니다. 하지만 보통 옵저버 패턴은 다음과 같은 식으로 정의됩니다.
옵저버 패턴(Obserber Pattern)에서는 한 객체의 상태가 바뀌면
그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 (one-to-many)의존성을 정의합니다.
1. Subject 인터페이스
public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
2. WeatherData 클래스
1. WeatherData 클래스에는 세 가지 측정값(온도, 습도, 기압)을 알아내기위한 게터 세터가 있습니다.
2. 새로운 기상 측정 데이터가 나올 때마다 measurementChanged()메소드가 호출됩니다. (이 메소드가 어떤 원리로 호출되는지는 알지도 못하고 꼭 알아야하는것도 아닙니다. 호출된다는것이 중요합니다.)
3. 기상데이터를 사용하는 세개의 디스플레이 항목을 구현해야 합니다. 새로운 측정값이 들어올 때마다 디스플레이를 갱신해야 합니다.
package weather_week_1;
import java.util.*;
public class WeatherData implements Subject {
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
private boolean changed = false;
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 (Observer observer : observers) {
// observer.update(temperature, humidity, pressure); //push 방식
observer.update(this); // pull 방식
}
}
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 void setTemperature(float temperature) {
this.temperature = temperature;
}
public void setHumidity(float humidity) {
this.humidity = humidity;
}
public void setPressure(float pressure) {
this.pressure = pressure;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
3. Observer 인터페이스
public interface Observer {
// public void update(float temp, float humidity, float pressure); //Push 방식
public void update(Subject subject); //Pull 방식
}
4. DisplayElement 인터페이스
public interface DisplayElement {
public void display();
}
5. CurrentConditionsDisplay 클래스
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private String measuredTime;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
// Push방식일때 이거씁니다.
// public void update(float temperature, float humidity, float pressure) {
// this.temperature = temperature;
// this.humidity = humidity;
// display();
// }
//Pull 방식
public void update(Subject subject) {
TimedWeatherData weatherData = (TimedWeatherData)subject;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
this.measuredTime = weatherData.getMeasuredTime();
display();
}
public void display() {
System.out.println("[Time: " + measuredTime +"]");
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
6. StatisticsDisplay 클래스
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);
}
// Push방식
// public void update(float temp, float humidity, float pressure) {
// tempSum += temp;
// numReadings++;
//
// if (temp > maxTemp) {
// maxTemp = temp;
// }
//
// if (temp < minTemp) {
// minTemp = temp;
// }
//
// display();
// }
//Pull 방식
public void update(Subject subject) {
WeatherData weatherData = (WeatherData) subject;
float temp = weatherData.getTemperature();
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);
}
}
7. ForecastDisplay 클래스
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
private WeatherData weatherData;
public ForecastDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
//Push 방식
// public void update(float temp, float humidity, float pressure) {
// lastPressure = currentPressure;
// currentPressure = pressure;
//
// display();
// }
//Pull 방식
public void update(Subject subject) {
WeatherData weatherData = (WeatherData) subject;
lastPressure = currentPressure;
currentPressure = weatherData.getPressure();
display();
}
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
8. WeatherStation 클래스
import java.util.Timer;
import java.util.TimerTask;
public class WeatherStation {
static WeatherData weatherData;
static float temperature = 80.0f;
static float lastTemperature = 80.0f;
static float humidity = 65.0f;
static float pressure = 30.0f;
public static void main(String[] args) {
System.out.println("실행합니다.");
weatherData = new WeatherData();
//weatherData = new TimedWeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
Timer jobScheduler = new Timer();
TimerTask task = new TimerTask() {
public void run() {
temperature = (float) (temperature + Math.random() * 0.1);
humidity = (float) (humidity + Math.random() * 0.1);
pressure = (float) (pressure + Math.random() * 0.1);
weatherData.setMeasurements(temperature, humidity, pressure);
}
};
jobScheduler.schedule(task, 0, 1000);
}
}
9. (번외) TimedWeatherData 클래스
import java.util.Date;
public class TimedWeatherData extends WeatherData {
private String measuredTime;
public TimedWeatherData() {
}
public String getMeasuredTime() {
return measuredTime;
}
public void setMeasuredTime(String measuredTime) {
this.measuredTime = measuredTime;
}
public void setMeasurements(float temperature, float humidity, float pressure) {
// 오버라이딩 되어서 구현되어야 함.
// measuredTime 정보도 설정되어야 함.
this.measuredTime = new Date().toString();
setTemperature(temperature);
setHumidity(humidity);
setPressure(pressure);
measurementsChanged();
}
}