2021. 2. 1. 23:15ㆍ개발/디자인패턴
Head First Design Patterns를 읽고 학습한 내용을 기록합니다.
정의
옵저버 패턴에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.
특정 객체에 변경이 발생했을때 그 객체에 의존하는 모든 객체에게 변경 사실을 알려 줄 수 있는 디자인 패턴이다.
책에선 신문 발행-구독을 예로 설명하고 있다. 여기서 정보를 구독하는 구독자를 옵저버(Observer)라고 부른다.
옵저버 패턴에서 발행자와 구독자는 느슨한 결합으로 연결되어 있는데 이 뜻은 두 객체가 상호작용을 하긴 하지만 서로가 내부적으로 어떤일을 하는지는 관심이 없다는 걸 의미한다. 발행자는 데이터를 제공만 할 뿐이지 구독자들이 이 데이터로 뭘하는지는 모른다. 따라서 발행자는 구독자의 목록만 관리하면 된다.
느슨하게 결합하는 디자인을 사용하면 결합 객체를 추가해야 하는 상황이 발생했을때 유연하게 처리할 수 있다. 객체 사이의 상호 의존성을 최소화 했기 때문에 얻을 수 있는 이점 중 하나다.
예시
상황셋팅.
날씨정보를 발행자가 제공하면 구독자는 자신의 입맛에 맞게 데이터를 가공해서 출력하자.
Subject 인터페이스에 날씨데이터 발행자가 할 수 있는 행동들을 정의한다.
날씨 데이터 발행자는 신규 구독자를 등록하거나(registerObserver), 아니면 기존의 구독자를 구독리스트에서 제외(removeObserver) 할 수 도있다. 물론, 현재 구독중인 구독자 목록도 확인(notifyObserver) 할 수 있어야겠다.
Observer 인터페이스에는 구독자가 할수 있는 행동을 정의한다.
구독자는 발행자로 부터 받은 정보를 가지고 자신의 상태를 변경할 수 있다.(update)
발행자가 습도,기온,기압 등의 정보를 발행한다면 해당 정보를 받는 구독자는 습도,기온,기압을 자신의 입맛에 맞게 가공 할 수 있어야 한다.
인터페이스는 발행자 그리고 구독자가 할 수 있는 행위만 정의되어 있고 누가 이 행위를 할지는 아직 구현되지 않았다. 이제 위 행위를 수행할 발행자와 구독자를 구현하자.
발행자의 클래스명을 WeatherData라고 지었다. 해당 클래스는 Subject 인터페이스를 구현함으로써 구독자를 관리한다. 또한 구독자에게 전달 할 날씨정보(기온,습도,기압)를 가지고 있다.
구독자 구현 클래스를 StatisticsDisplay, ForecaseDisplay로 생성했다. 이름에서 알수 있듯이 StatisticsDisplay는 통계에 대한 정보를 처리하는 구독자이며, ForecaseDisplay는 예보에 대한 정보를 처리하는 구독자다.
소스코드 구현
발행자와 구독자를 만들었으니 이제 소스코드를 통해 관계를 부여하자.
먼저 발행자의 구현 Class인 WeatherData의 소스코드.
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
|
import java.util.ArrayList;
public class WeatherData implements Subject{
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
// 구독자를 담을 List를 정의하자.
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();
}
}
|
cs |
구독자 중 하나인 StatisticsDisplay의 소스코드.
Observer 인터페이스를 구현하고 있다. ( DisplayElement 인터페이스는 출력에 대해 정의된 인터페이스 )
여기서 잘 봐야할 부분은 생성자인데 WeatherData 객체를 입력으로 받아서 자기 자신을 등록한다.
쉽게 생각하면 발행자를 직접 불러와서 자신을 등록한다고 보면된다.
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
|
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;
// weatherData에 나(StatisticsDisplay 객체)를 등록한다
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 |
구독자 등록을 위한 메인 메소드. 먼저 발행자 객체인 WeatherData를 선언한다. 그리고 구독자 객체를 선언하면서 생성자의 파라미터로 발행자 객체를 넘겨준다. 앞선 코드에서 봤듯이 구독자 객체는 생성자를 통해 발행자를 넘겨받고 구독자 객체 내에서 구독 신청을 하게 된다.
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 WeatherStation {
public static void main(String[] args) {
// 발행자 객체 생성
WeatherData weatherData = new WeatherData();
// 구독자 객체를 생성하면서 weatherData를 구독하도록 셋팅
StatisticsDisplay sd = new StatisticsDisplay(weatherData);
ForecastDisplay fd = new ForecastDisplay(weatherData);
System.out.println("최초등록");
sd.display();
fd.display();
System.out.println("\n정보 입력1");
weatherData.setMeasurements(80, 65, 30.4f);
System.out.println("\n정보 입력2");
weatherData.setMeasurements(82, 70, 29.2f);
System.out.println("\n정보 입력3 ( + 구독자 제거 )");
weatherData.removeObserver(fd);
weatherData.setMeasurements(62, 90, 28.1f);
}
}
|
cs |
위코드의 출력 결과.
'개발 > 디자인패턴' 카테고리의 다른 글
[디자인패턴] 커맨드 패턴(Command Pattern) (0) | 2021.03.07 |
---|---|
[Design Pattern] 싱글턴 패턴(Singleton Pattern) (0) | 2021.03.05 |
[Design Pattern] 팩토리 패턴(Factory Pattern) (1) | 2021.03.02 |
[Design Pattern] 데코레이터 패턴(Decorator Pattern) (0) | 2021.02.05 |
[Design Pattern] 전략패턴(Strategy Pattern) (0) | 2021.01.29 |