목표
1. 옵저버 패턴 정의
옵저버 패턴은 한 객체의 상태가 변경되면 그 객체에 의존하는 다른 객체들에게 연락이 가고 자동으로 내용이 변경되는 일대다 의존성을 정의합니다
사용 시점
객체 간의 일대다 의존성을 정의하고 싶을 때
한 객체의 상태가 변경될 때 다른 객체들이 자동으로 알림을 받고 싶을 때
느슨한 결합을 유지하면서 객체 간 통신이 필요할 때
클래스 다이어그램 classDiagram
class Subject {
<<interface>>
+registerObserver(observer: Observer)
+removeObserver(observer: Observer)
+notifyObservers()
}
class Observer {
<<interface>>
+update(temperature: float, humidity: float, pressure: float)
}
class DisplayElement {
<<interface>>
+display()
}
class WeatherData {
-observers: List~Observer~
-temperature: float
-humidity: float
-pressure: float
+registerObserver(observer: Observer)
+removeObserver(observer: Observer)
+notifyObservers()
+measurementsChanged()
+setMeasurements(temperature: float, humidity: float, pressure: float)
}
class CurrentConditionsDisplay {
-weatherData: Subject
-temperature: float
-humidity: float
+CurrentConditionsDisplay(weatherData: Subject)
+update(temperature: float, humidity: float, pressure: float)
+display()
}
class StatisticsDisplay {
-weatherData: Subject
-maxTemp: float
-minTemp: float
-tempSum: float
-numReadings: int
+StatisticsDisplay(weatherData: Subject)
+update(temperature: float, humidity: float, pressure: float)
+display()
}
class ForecastDisplay {
-weatherData: Subject
-currentPressure: float
-lastPressure: float
+ForecastDisplay(weatherData: Subject)
+update(temperature: float, humidity: float, pressure: float)
+display()
}
%% 인터페이스 구현 관계
Subject <|.. WeatherData : implements
Observer <|.. CurrentConditionsDisplay : implements
Observer <|.. StatisticsDisplay : implements
Observer <|.. ForecastDisplay : implements
DisplayElement <|.. CurrentConditionsDisplay : implements
DisplayElement <|.. StatisticsDisplay : implements
DisplayElement <|.. ForecastDisplay : implements
%% 구성 관계 (Composition)
WeatherData *-- Observer : has
%% 의존 관계 (Dependency)
CurrentConditionsDisplay --> Subject : observes
StatisticsDisplay --> Subject : observes
ForecastDisplay --> Subject : observes
예시 코드 기상 스테이션과 다양한 디스플레이를 구현한 예시를 통해 옵저버 패턴을 살펴보겠습니다.
1. 주제(Subject) 인터페이스 1 2 3 4 5 public interface Subject { void registerObserver (Observer observer) ; void removeObserver (Observer observer) ; void notifyObservers () ; }
2. 옵저버(Observer) 인터페이스 1 2 3 public interface Observer { void update (float temperature, float humidity, float pressure) ; }
3. 디스플레이 인터페이스 1 2 3 public interface DisplayElement { void display () ; }
4. 기상 데이터 클래스 (주제) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import java.util.ArrayList;import java.util.List;public class WeatherData implements Subject { private List<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData () { observers = new ArrayList <>(); } @Override public void registerObserver (Observer observer) { observers.add(observer); } @Override public void removeObserver (Observer observer) { observers.remove(observer); } @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(); } }
5. 현재 조건 디스플레이 (옵저버) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class CurrentConditionsDisplay implements Observer , DisplayElement { private Subject weatherData; private float temperature; private float humidity; public CurrentConditionsDisplay (Subject 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 + "% 습도" ); } }
6. 통계 디스플레이 (옵저버) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class StatisticsDisplay implements Observer , DisplayElement { private Subject weatherData; private float maxTemp = 0.0f ; private float minTemp = 200.0f ; private float tempSum = 0.0f ; private int numReadings = 0 ; public StatisticsDisplay (Subject weatherData) { this .weatherData = weatherData; weatherData.registerObserver(this ); } @Override public void update (float temperature, float humidity, float pressure) { tempSum += temperature; numReadings++; if (temperature > maxTemp) { maxTemp = temperature; } if (temperature < minTemp) { minTemp = temperature; } display(); } @Override public void display () { System.out.println("평균/최고/최저 온도 = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp); } }
7. 예보 디스플레이 (옵저버) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class ForecastDisplay implements Observer , DisplayElement { private Subject weatherData; private float currentPressure = 29.92f ; private float lastPressure; public ForecastDisplay (Subject weatherData) { this .weatherData = weatherData; weatherData.registerObserver(this ); } @Override public void update (float temperature, float humidity, float pressure) { lastPressure = currentPressure; currentPressure = pressure; display(); } @Override public void display () { System.out.print("예보: " ); if (currentPressure > lastPressure) { System.out.println("날씨가 좋아지고 있습니다!" ); } else if (currentPressure == lastPressure) { System.out.println("날씨가 그대로입니다." ); } else if (currentPressure < lastPressure) { System.out.println("비가 올 것 같습니다." ); } } }
8. 테스트 코드 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); System.out.println("=== 첫 번째 측정 ===" ); weatherData.setMeasurements(80 , 65 , 30.4f ); System.out.println("\n=== 두 번째 측정 ===" ); weatherData.setMeasurements(82 , 70 , 29.2f ); System.out.println("\n=== 세 번째 측정 ===" ); weatherData.setMeasurements(78 , 90 , 29.2f ); } }
실행 결과 1 2 3 4 5 6 7 8 9 10 11 12 13 14 === 첫 번째 측정 === 현재 조건: 80.0F 도, 65.0% 습도 평균/최고/최저 온도 = 80.0/80.0/80.0 예보: 날씨가 좋아지고 있습니다! === 두 번째 측정 === 현재 조건: 82.0F 도, 70.0% 습도 평균/최고/최저 온도 = 81.0/82.0/80.0 예보: 비가 올 것 같습니다. === 세 번째 측정 === 현재 조건: 78.0F 도, 90.0% 습도 평균/최고/최저 온도 = 80.0/82.0/78.0 예보: 날씨가 그대로입니다.
옵저버 패턴 장점
느슨한 결합: 주제와 옵저버가 느슨하게 결합되어 있습니다.
확장성: 새로운 옵저버를 쉽게 추가할 수 있습니다.
일대다 관계: 하나의 주제가 여러 옵저버에게 알림을 보낼 수 있습니다.
자동 알림: 주제의 상태가 변경되면 모든 옵저버가 자동으로 알림을 받습니다.
정리
옵저버 패턴은 객체 간의 일대다 의존성을 정의할 때 사용합니다.
한 객체의 상태가 변경될 때 다른 객체들이 자동으로 알림을 받고 싶을 때 사용합니다.
느슨한 결합을 유지하면서 객체 간 통신이 필요할 때 사용합니다.
ref