Posts [개발자 블로그] 옵서버 패턴(Observer Pattern)
Post
Cancel

[개발자 블로그] 옵서버 패턴(Observer Pattern)

옵서버 패턴(Observer Pattern)

  • 데이터의 변경이 발생하였을 때 상대 클래스 및 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때
  • 옵서버 패턴은 통보 대상 객체의 관리를 Subject 클래스와 Observer 인터페이스로 일반화한다. 그러면 데이터 변경을 통보하는 클래스(ConcreteSubject)는 통보 대상 클래스/객체ConcreteObserver)에 대한 의존성을 제거할 수 있다. 결과적으로 옵서버 패턴은 통보 대상 클래스나 대상 객체의 변경에도 ConcreteSubject 클래스를 수정 없이 그대로 사용할 수 있도록 한다.

성적 출력 프로그램 example

  • ScoreRecord 클래스: 점수를 저장/관리하는 클래스
  • DataSheetView 클래스: 점수를 목록형태로 출력하는 클래스 image
  • 성적 출력 프로그램의 순차 다이어그램
    image
  • 소스 코드
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(관찰 대상)을 알아야 함
    스크린샷 2021-03-10 오후 7 15 50

  • 소스 코드
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);
		}		
	}
}

옵서버 패턴의 일반적인 형태

image

옵서버 패턴의 순차 다이어그램

image

옵서버 패턴을 위에 적용한 클래스 다이어그램

image

출처

  • Java객체지향 디자인패턴(한빛미디어)
This post is licensed under CC BY 4.0 by the author.

[개발자 블로그] 컴퍼지트 패턴(Composite Pattern)

[개발자 블로그] 스트래티지 패턴(Strategy Pattern)