자바/자바 자료실

[디자인패턴] 02. 옵저버 패턴 (Observer Pattern)

Chipmunks 2018. 10. 19.
728x90


옵저버 패턴 (Observer Pattern)

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의합니다.


객체지향 원칙

느슨한 결합 (Loose Coupling)

서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.


Java.util 을 이용한 옵저버 패턴 구현

단점 및 한계

Observable 은 인터페이스가 아닌 클래스이고, 어떠한 인터페이스를 구현하는 방법도 아닙니다. 이를 구현하기 위해 서브클래스를 꼭 만들어야 합니다. 그러나 이미 다른 상위 클래스를 확장하고 있는 클래스에 Observable 의 기능을 추가할 수 없습니다. 재사용성에 제약이 생깁니다. 인터페이스를 구현한 것이 아니기 때문에 다른 구현으로 바꾸는 것이 불가능합니다.


Observable 클래스의 핵심 메소드를 외부에서 호출할 수 없습니다. Observable API 에서의 setChanged() 메소드가 protected 로 선언되어 있습니다. Observable의 서브클래스에서만 setChanged() 메소드를 호출할 수 있습니다. 따라서 구성을 이용한 디자인 원칙을 사용할 수 없습니다.


다이어그램




예제 코드

1. Observer, Subject 인터페이스를 직접 구현하기

- weather/Observer.java

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(806530.4f);
        weatherData.setMeasurements(827029.2f);
        weatherData.setMeasurements(789029.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(806530.4f);
        weatherData.setMeasurements(827029.2f);
        weatherData.setMeasurements(789029.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(806530.4f);
        weatherData.setMeasurements(827029.2f);
        weatherData.setMeasurements(789029.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 에서의 옵저버 패턴 엿보기

- swing/SwingObserverExample.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
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


댓글