옵서버 패턴(Observer Pattern)
- 데이터의 변경이 발생하였을 때 상대 클래스 및 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때
- 옵서버 패턴은 통보 대상 객체의 관리를 Subject 클래스와 Observer 인터페이스로 일반화한다. 그러면 데이터 변경을 통보하는 클래스(ConcreteSubject)는 통보 대상 클래스/객체ConcreteObserver)에 대한 의존성을 제거할 수 있다. 결과적으로 옵서버 패턴은 통보 대상 클래스나 대상 객체의 변경에도 ConcreteSubject 클래스를 수정 없이 그대로 사용할 수 있도록 한다.
성적 출력 프로그램 example
- ScoreRecord 클래스: 점수를 저장/관리하는 클래스
- DataSheetView 클래스: 점수를 목록형태로 출력하는 클래스
- 성적 출력 프로그램의 순차 다이어그램
- 소스 코드
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
public class ScoreRecord {
private List<Integer> scores = new ArrayList<Integer>(); // 점수를 저장함
private DataSheetView dataSheetView; // 목록 형태로 점수를 출력하는 클래스
public void setDataSheetView(DataSheetView dataSheetView) {
this.dataSheetView = dataSheetView;
}
public void addScore(int score) { // 새로운 점수를 축함
scores.add(score); // scores 목록에 주어진 점수를 추가함
dataSheetView.update(); // scores가 변경됨을 통보함
}
public List<Integer> getScoreRecord() {
return scores;
}
}
public class DataSheetView {
private ScoreRecord scoreRecord;
private int viewCount;
public DataSheetView(ScoreRecord scoreRecord, int viewCount) {
this.scoreRecord = scoreRecord;
this.viewCount = viewCount;
}
public void update() { // 점수의 변경을 통보 받음
List<Integer> record = scoreRecord.getScoreRecord(); // 점수를 조회함
displayScores(record, viewCount); // 조회된 점수를 viewCount만큼 출력함
}
private void displayScores(List<Integer> record, int viewCount) {
System.out.print("List of " + viewCount + " entries: ");
for ( int i = 0; i < viewCount && i < record.size(); i ++ ) {
System.out.print(record.get(i) + " ");
}
System.out.println();
}
}
public class Client {
public static void main(String[] args) {
ScoreRecord scoreRecord = new ScoreRecord();
// 3개까지의 점수만 출력함
DataSheetView dataSheetView = new DataSheetView(scoreRecord, 3);
scoreRecord.setDataSheetView(dataSheetView);
for (int index = 1; index <= 5; index ++ ) {
int score = index * 10;
System.out.println("Adding " + score);
// 10 20 30 40 50을 추가함, 추가할 때마다 최대 3개의 점수만 출력함
scoreRecord.addScore(score);
}
}
}
문제점
- 1) 성적을 다른 방식으로 출력하고 싶다면 어떤 변경 작업을 해야 하는가? 예를 들어 성적을 목록으로 출력하지 않고 최소/최대값만을 출력하려면?
- => MinMaxView를 이용하도록 소스코드가 수정되었음
- => 기능 변경을 위해서 기존 소스 코드를 수정하므로 OCP를 위반하는 것임
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
public class MinMaxView { // 전체 점수가 아니라 최소/최대값만을 출력하는 클래스
private ScoreRecord scoreRecord;
public MinMaxView(ScoreRecord scoreRecord) {
this.scoreRecord = scoreRecord;
}
public void update() {
List<Integer> record = scoreRecord.getScoreRecord();
displayMinMax(record); // 최소/최대값만을 출력
}
private void displayMinMax(List<Integer> record) {
int min = Collections.min(record, null);
int max = Collections.max(record, null);
System.out.println("Min: " + min + " Max: " + max);
}
}
public class ScoreRecord {
private List<Integer> scores = new ArrayList<Integer>();
private MinMaxView minMaxView;
public void setStatisticsView(MinMaxView minMaxView) { // MinMaxView를 설정함
this.minMaxView = minMaxView;
}
public void addScore(int score) {
scores.add(score);
minMaxView.update(); // MinMaxView에게 점수의 변경을 통보함
}
public List<Integer> getScoreRecord() {
return scores;
}
}
public class Client {
public static void main(String[] args) {
ScoreRecord scoreRecord = new ScoreRecord();
MinMaxView minMaxView = new MinMaxView(scoreRecord);
scoreRecord.setMinMaxView(minMaxView);
for (int index = 1; index <= 5; index ++ ) {
int score = index * 10;
System.out.println("Adding " + score);
// 10 20 30 40 50을 추가함, 추가할 때마다 최소/최대 점수만 출력함
scoreRecord.addScore(score);
}
}
}
- 2) 성적을 동시에 여러 가지 형태로 출력하려면 어떤 변경 작업을 해야 하는가? 예를 들어 성적이 입력되면 최대 3개 목록으로 출력, 최대 5개 목록으로 출력 그리고 동시에 최소/최대값만을 출력하려면?
- => 기능 변경을 위해서 기존 소스 코드를 수정하므로 OCP를 위반하는 것임
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class ScoreRecord {
private List<Integer> scores = new ArrayList<Integer>();
private List<DataSheetView> dataSheetViews = new ArrayList<DataSheetView>();
private MainMaxView minMaxView;
public void addDataSheetView(DataSheetView dataSheetView) {
dataSheetViews.add(dataSheetView);
}
public void setMinMaxView(MainMaxView minMaxView) {
this.minMaxView = minMaxView;
}
public void addScore(int score) {
scores.add(score);
for ( DataSheetView dataSheetView: dataSheetViews )
dataSheetView.update(); // 각 DataSheetView에게 점수의 변경을 통보
minMaxView.update(); // MinMaxView에게 점수의 변경을 통보
}
public List<Integer> getScoreRecord() { return scores; }
}
public class DataSheetView {
private ScoreRecord scoreRecord;
private int viewCount;
public DataSheetView(ScoreRecord scoreRecord, int viewCount) {
this.scoreRecord = scoreRecord;
this.viewCount = viewCount;
}
public void update() { // 점수의 변경을 통보 받음
List<Integer> record = scoreRecord.getScoreRecord(); // 점수를 조회함
displayScores(record, viewCount); // 조회된 점수를 viewCount만큼 출력함
}
private void displayScores(List<Integer> record, int viewCount) {
System.out.print("List of " + viewCount + " entries: ");
for ( int i = 0; i < viewCount && i < record.size(); i ++ ) {
System.out.print(record.get(i) + " ");
}
System.out.println();
}
}
public class MinMaxView { // 전체 점수가 아니라 최소/최대값만을 출력하는 클래스
private ScoreRecord scoreRecord;
public MinMaxView(ScoreRecord scoreRecord) {
this.scoreRecord = scoreRecord;
}
public void update() {
List<Integer> record = scoreRecord.getScoreRecord();
displayMinMax(record); // 최소/최대값만을 출력
}
private void displayMinMax(List<Integer> record) {
int min = Collections.min(record, null);
int max = Collections.max(record, null);
System.out.println("Min: " + min + " Max: " + max);
}
}
public class Client {
public static void main(String[] args) {
ScoreRecord scoreRecord = new ScoreRecord();
// 3개 목록의 DataSheetView 생성
DataSheetView dataSheetView3 = new DataSheetView(scoreRecord, 3);
// 5개 목록의 DataSheetView 생성
DataSheetView dataSheetView5 = new DataSheetView(scoreRecord, 5);
MainMaxView minMaxView = new MainMaxView(scoreRecord);
scoreRecord.addDataSheetView(dataSheetView3);
scoreRecord.addDataSheetView(dataSheetView5);
scoreRecord.setMinMaxView(minMaxView);
for (int index = 1; index <= 5; index ++ ) {
int score = index * 10;
System.out.println("Adding " + score);
// 10 20 30 40 50을 추가함
// 추가할 때마다 최대 3개목록, 최대 5개 목록, 그리고 최소/최대 점수가 출력됨
scoreRecord.addScore(score);
}
}
}
- 3) 프로그램이 실행 시에 성적의 출력 대상이 변경되는 것을 지원한다면 어떤 변경 작업을 해야 하는가? 예를 들어 처음에는 목록으로 출력하고 나중에는 최소/최대값을 출력하려면?
해결책
- 성적 통보 대상이 변경되더라도 ScoreRecord 클래스를 그대로 재사용할 수 있어야 함
- 관찰 대상은 누가 나를 관찰하는지 몰라도 됨(ScoreRecord는 새롭게 추가되는 View에 영향을 안받는다.)
새롭게 추가되는 View는 ScoreRecord(관찰 대상)을 알아야 함
- 소스 코드
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public interface Observer { // 추상화된 통보 대상
abstract public void update();
}
public abstract class Subject { // 추상화된 변경 관심 대상 데이터
private List<Observer> observers = new ArrayList<Observer>();
public void attach(Observer observer) { // 옵서버 즉 통보 대상을 추가함
observers.add(observer);
}
public void detach(Observer observer) { // 옵서버 즉 통보 대상을 제거함
observers.remove(observer);
}
// 통보 대상 목록, 즉 observers의 각 옵서버에게 변경을 통보함
public void notifyObservers() {
for ( Observer o : observers ) o.update();
}
}
public class ScoreRecord extends Subject { // 구체적인 변경 감시 대상 데이터
private List<Integer> scores = new ArrayList<Integer>();
public void addScore(int score) {
scores.add(score);
// 데이터가 변경되면 Subject 클래스의 notifyObservers 메서드를 호출해
// 각 옵서버(통보 대상 객체)에게 데이터의 변경을 통보함
notifyObservers();
}
public List<Integer> getScoreRecord() {
return scores;
}
}
// DataSheetView는 Observer의 기능 즉 update 메서드를 구현함으로써 통보 대상이 됨
public class DataSheetView implements Observer {
private ScoreRecord scoreRecord;
private int viewCount;
public DataSheetView(ScoreRecord scoreRecord, int viewCount) {
this.scoreRecord = scoreRecord;
this.viewCount = viewCount;
}
public void update() { // 점수의 변경을 통보 받음
List<Integer> record = scoreRecord.getScoreRecord(); // 점수를 조회함
displayScores(record, viewCount); // 조회된 점수를 viewCount만큼 출력함
}
private void displayScores(List<Integer> record, int viewCount) {
System.out.print("List of " + viewCount + " entries: ");
for ( int i = 0; i < viewCount && i < record.size(); i ++ ) {
System.out.print(record.get(i) + " ");
}
System.out.println();
}
}
// MinMaxView는 Observer의 기능 즉 update 메서드를 구현함으로써 통보 대상이 됨
public class MinMaxView implements Observer {
private ScoreRecord scoreRecord;
public MinMaxView(ScoreRecord scoreRecord) {
this.scoreRecord = scoreRecord;
}
public void update() {
List<Integer> record = scoreRecord.getScoreRecord();
displayMinMax(record); // 최소/최대값만을 출력
}
private void displayMinMax(List<Integer> record) {
int min = Collections.min(record, null);
int max = Collections.max(record, null);
System.out.println("Min: " + min + " Max: " + max);
}
}
public class Client {
public static void main(String[] args) {
ScoreRecord scoreRecord = new ScoreRecord();
DataSheetView dataSheetView3 = new DataSheetView(scoreRecord, 3);
DataSheetView dataSheetView5 = new DataSheetView(scoreRecord, 5);
MinMaxView minMaxView = new MinMaxView(scoreRecord);
// 3개 목록 DataSheetView를 ScoreRecord에 Observer로 추가함
scoreRecord.attach(dataSheetView3);
// 5개 목록 DataSheetView를 ScoreRecord에 Observer로 추가함
scoreRecord.attach(dataSheetView5);
// MinMaxView를 ScoreRecord에 Observer로 추가함
scoreRecord.attach(minMaxView);
for (int index = 1; index <= 5; index ++ ) {
int score = index * 10;
System.out.println("Adding " + score);
scoreRecord.addScore(score);
}
}
}
옵서버 패턴의 일반적인 형태
옵서버 패턴의 순차 다이어그램
옵서버 패턴을 위에 적용한 클래스 다이어그램
출처
- Java객체지향 디자인패턴(한빛미디어)