옵저버 패턴 (Observer Pattern)
객체지향 원칙
느슨한 결합 (Loose Coupling)
Java.util 을 이용한 옵저버 패턴 구현
단점 및 한계
Observable 은 인터페이스가 아닌 클래스이고, 어떠한 인터페이스를 구현하는 방법도 아닙니다. 이를 구현하기 위해 서브클래스를 꼭 만들어야 합니다. 그러나 이미 다른 상위 클래스를 확장하고 있는 클래스에 Observable 의 기능을 추가할 수 없습니다. 재사용성에 제약이 생깁니다. 인터페이스를 구현한 것이 아니기 때문에 다른 구현으로 바꾸는 것이 불가능합니다.
Observable 클래스의 핵심 메소드를 외부에서 호출할 수 없습니다. Observable API 에서의 setChanged() 메소드가 protected 로 선언되어 있습니다. Observable의 서브클래스에서만 setChanged() 메소드를 호출할 수 있습니다. 따라서 구성을 이용한 디자인 원칙을 사용할 수 없습니다.
다이어그램
예제 코드
1. Observer, Subject 인터페이스를 직접 구현하기
1 2 3 4 5 6 | package weather; public interface Observer { public void update(float temperature, float humidity, float pressure); } | cs |
- weather/Subject.java
1 2 3 4 5 6 7 8 | package weather; public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); } | cs |
- weather/WeatherData.java
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package weather; import java.util.ArrayList; public class WeatherData implements Subject { private ArrayList observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } @Override public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { Observer observer = (Observer) observers.get(i); 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(); } // 기타 WeatherData 메소드 public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } } | cs |
- weather/DisplayElement.java
1 2 3 4 5 6 | package weather; public interface DisplayElement { public void display(); } | cs |
- weather/CurrentConditionsDisplay
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 | package weather; public class CurrentConditionsDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; display(); } } | cs |
- weather/StatisticsDisplay.java
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 | package weather; 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); } } | cs |
- weather/ForecastDisplay.java
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 | package weather; 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); } public void update(float temp, float humidity, float pressure) { lastPressure = currentPressure; currentPressure = pressure; 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"); } } } | cs |
- weather/HeatIndexDisplay.java
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 | package weather; public class HeatIndexDisplay implements Observer, DisplayElement { float heatIndex = 0.0f; private WeatherData weatherData; public HeatIndexDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } public void update(float t, float rh, float pressure) { heatIndex = computeHeatIndex(t, rh); display(); } private float computeHeatIndex(float t, float rh) { float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + 0.000000000843296 * (t * t * rh * rh * rh)) - (0.0000000000481975 * (t * t * t * rh * rh * rh))); return index; } public void display() { System.out.println("Heat index is " + heatIndex); } } | cs |
- weather/WeatherStation.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package weather; 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); HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } } | cs |
- 실행 결과
1 2 3 4 5 6 7 8 9 10 11 12 13 | Current conditions: 80.0F degrees and 65.0% humidity Avg/Max/Min temperature = 80.0/80.0/80.0 Forecast: Improving weather on the way! Heat index is 82.95535 Current conditions: 82.0F degrees and 70.0% humidity Avg/Max/Min temperature = 81.0/82.0/80.0 Forecast: Watch out for cooler, rainy weather Heat index is 86.90124 Current conditions: 78.0F degrees and 90.0% humidity Avg/Max/Min temperature = 80.0/82.0/78.0 Forecast: More of the same Heat index is 83.64967 | cs |
2. Java.util 의 Observable 클래스 상속하여 옵저버 패턴 구현하기
- weatherobservable/DisplayElement.java
1 2 3 4 5 6 | package weatherobservable; public interface DisplayElement { public void display(); } | cs |
- weatherobservable/WeatherData.java
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 | package weatherobservable; import java.util.Observable; public class WeatherData extends Observable { private float temperature; private float humidity; private float pressure; public WeatherData() { } public void measurementsChanged() { setChanged(); notifyObservers(); } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } // 기타 WeatherData 메소드 public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } } | cs |
- weatherobservable/CurrentConditionsDisplay.java
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 | package weatherobservable; import java.util.Observable; import java.util.Observer; public class CurrentConditionsDisplay implements Observer, DisplayElement { Observable observable; private float temperature; private float humidity; public CurrentConditionsDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } @Override public void update(Observable obs, Object arg) { if (obs instanceof WeatherData) { WeatherData weatherData = (WeatherData) obs; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); display(); } } public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } } | cs |
- weatherobservable/StatisticsDisplay.java
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 | package weatherobservable; import java.util.Observable; import java.util.Observer; public class StatisticsDisplay implements Observer, DisplayElement { private float maxTemp = 0.0f; private float minTemp = 200; private float tempSum= 0.0f; private int numReadings; public StatisticsDisplay(Observable observable) { observable.addObserver(this); } public void update(Observable observable, Object arg) { if (observable instanceof WeatherData) { WeatherData weatherData = (WeatherData)observable; 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); } } | cs |
- weatherobservable/ForecastDisplay.java
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 | package weatherobservable; import java.util.Observable; import java.util.Observer; public class ForecastDisplay implements Observer, DisplayElement { Observable observable; private float currentPressure = 29.92f; private float lastPressure; public ForecastDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } public void update(Observable obs, Object arg) { if (obs instanceof WeatherData) { WeatherData weatherData = (WeatherData) obs; 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"); } } } | cs |
- weatherobservable/HeatIndexDisplay.java
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 42 43 | package weatherobservable; import java.util.Observable; import java.util.Observer; public class HeatIndexDisplay implements Observer, DisplayElement { float heatIndex = 0.0f; public HeatIndexDisplay(Observable observable) { observable.addObserver(this); } public void update(Observable observable, Object arg) { if (observable instanceof WeatherData) { WeatherData weatherData = (WeatherData)observable; float t = weatherData.getTemperature(); float rh = weatherData.getHumidity(); heatIndex = (float) ( (16.923 + (0.185212 * t)) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + (0.000000000843296 * (t * t * rh * rh * rh)) - (0.0000000000481975 * (t * t * t * rh * rh * rh))); display(); } } public void display() { System.out.println("Heat index is " + heatIndex); } } | cs |
- weatherobservable/WeatherStation.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package weatherobservable; public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditions = 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); } } | cs |
- 실행결과
1 2 3 4 5 6 7 8 9 10 | Forecast: Improving weather on the way! Avg/Max/Min temperature = 80.0/80.0/80.0 Current conditions: 80.0F degrees and 65.0% humidity Forecast: Watch out for cooler, rainy weather Avg/Max/Min temperature = 81.0/82.0/80.0 Current conditions: 82.0F degrees and 70.0% humidity Forecast: More of the same Avg/Max/Min temperature = 80.0/82.0/78.0 Current conditions: 78.0F degrees and 90.0% humidity | cs |
- weatherobservable/WeatherStationHeatIndex.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package weatherobservable; public class WeatherStationHeatIndex { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } } | cs |
- 실행결과
1 2 3 4 5 6 7 8 9 10 11 12 13 | Heat index is 82.95535 Forecast: Improving weather on the way! Avg/Max/Min temperature = 80.0/80.0/80.0 Current conditions: 80.0F degrees and 65.0% humidity Heat index is 86.90124 Forecast: Watch out for cooler, rainy weather Avg/Max/Min temperature = 81.0/82.0/80.0 Current conditions: 82.0F degrees and 70.0% humidity Heat index is 83.64967 Forecast: More of the same Avg/Max/Min temperature = 80.0/82.0/78.0 Current conditions: 78.0F degrees and 90.0% humidity | cs |
3. Swing API 에서의 옵저버 패턴 엿보기
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package swing; import java.awt.*; import javax.swing.*; public class SwingObserverExample { JFrame frame; public static void main(String[] args) { SwingObserverExample example = new SwingObserverExample(); example.go(); } public void go() { frame = new JFrame(); JButton button = new JButton("Should I do it?"); // Without lambdas //button.addActionListener(new AngelListener()); //button.addActionListener(new DevilListener()); // With lambdas button.addActionListener(event -> System.out.println("Don't do it, you might regret it!") ); button.addActionListener(event -> System.out.println("Come on, do it!") ); frame.getContentPane().add(BorderLayout.CENTER, button); // Set frame properties frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(BorderLayout.CENTER, button); frame.setSize(300,300); frame.setVisible(true); } /* * Remove these two inner classes to use lambda expressions instead. * class AngelListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("Don't do it, you might regret it!"); } } class DevilListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("Come on, do it!"); } } */ } | cs |
'자바 > 자바 자료실' 카테고리의 다른 글
[디자인 패턴] 06. 커맨드 패턴 (Command Pattern) (0) | 2018.10.23 |
---|---|
[디자인 패턴] 05. 싱글턴 패턴 (Singleton Pattern) (0) | 2018.10.23 |
[디자인 패턴] 04. 팩토리 패턴 (Factory Pattern) (0) | 2018.10.23 |
[디자인패턴] 03. 데코레이터 패턴 (Decorator Pattern) (0) | 2018.10.22 |
[디자인패턴] 01. 스트래티지 패턴 (Strategy Pattern) (0) | 2018.10.19 |
댓글