옵저버 패턴을 이해한다.

Goal

  • 옵저버 패턴의 개념을 이해한다.
  • 예시를 통해 옵저버 패턴을 이해한다.

옵저버 패턴이란

참고

예시

여러 가지 방식으로 성적 출력하기

/* 입력된 점수를 저장하는 클래스 (1. 출력형태: 목록) */
public class ScoreRecord {
  private List<Integer> scores = new ArrayList<Integer>(); // 점수를 저장함
  private DataSheetView dataSheetView; // 목록 형태로 점수를 출력하는 클래스

  public void setDataSheetView(DataSheetView dataSheetView) { this.dataSheetView = dataSheetView; }
  // 새로운 점수를 추가하면 출력하는 것에 변화를 통보(update())하여 출력하는 부분 갱신
	public void addScore(int score) {
	   scores.add(score); // scores 목록에 주어진 점수를 추가함
	   dataSheetView.update(); // scores가 변경됨을 통보함
	}
	// 출력하는 부분에서 변화된 내용을 얻어감
	public List<Integer> getScoreRecord() { return scores; }
}
/* 1. 출력형태: 목록 형태로 출력하는 클래스 */
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.println("List of " + viewCount + " entries: ");
		for (int i = 0; i < viewCount && i < record.size(); i++) {
			System.out.println(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. 성적을 다른 형태로 출력하는 경우
    • 성적을 목록으로 출력하지 않고 성적의 최소/최대 값만 출력하려면?
      /* 입력된 점수를 저장하는 클래스 (2. 출력형태: 최대,최소값) */
      public class ScoreRecord {
       private List<Integer> scores = new ArrayList<Integer>(); // 점수를 저장함
       private MinMaxView minMaxView; // 최소/최대 값만을 출력하는 형태의 클래스
       // MinMaxView를 설정함
       public void setMinMaxView(MinMaxView minMaxView) { this.minMaxView = minMaxView; }
       // 새로운 점수를 추가하면 출력하는 것에 변화를 통보(update())하여 출력하는 부분 갱신
       public void addScore(int score) {
         scores.add(score); // scores 목록에 주어진 점수를 추가함
         minMaxView.update(); // MinMaxView에게 scores가 변경됨을 통보함
       }
       // 출력하는 부분에서 변화된 내용을 얻어감
       public List<Integer> getScoreRecord() { return scores; }
      }
      
      /* 2. 출력형태: 최소/최대 값만을 출력하는 형태의 클래스 */
      public class MinMaxView {
        private ScoreRecord scoreRecord;
        // getScoreRecord()를 호출하기 위해 ScoreRecord 객체를 인자로 받음
        public MinMaxView(ScoreRecord scoreRecord) {
         this.scoreRecord = scoreRecord;
        }
        // 점수의 변경을 통보받음
        public void update() {
         List<Integer> record = scoreRecord.getScoreRecord(); // 점수를 조회함
         displayScores(record); // 최소값과 최대값을 출력함
        }
        // 최소값과 최대값을 출력함
        private void displayScores(List<Integer> record) {
         int min = Collections.min(record, null);
         int max = Collections.max(record, null);
         System.out.println("Min: " + min + ", Max: " + max);
        }
      }
      
    • 점수 변경에 대한 통보 대상 클래스가 다른 대상 클래스(DataSheetView->MinMaxView)로 바뀌면 기존 코드(ScoreRecord 클래스)의 내용을 수정해야 하므로 OCP에 위배된다.
  2. 동시 혹은 순차적으로 성적을 출력하는 경우
    • 성적이 입력되었을 때 최대 3개 목록, 최대 5개 목록, 최소/최대 값을 동시에 출력하려면?
    • 처음에는 목록으로 출력하고 나중에는 최소/최대 값을 출력하려면?
      /* 입력된 점수를 저장하는 클래스 (3. 출력형태: 2개 출력 형태를 가질 때) */
      public class ScoreRecord {
        private List<Integer> scores = new ArrayList<Integer>(); // 점수를 저장함
        private DataSheetView dataSheetView; // 목록 형태로 점수를 출력하는 클래스
        private MinMaxView minMaxView; // 최소/최대 값만을 출력하는 형태의 클래스
        // DataSheetView를 설정함
        public void setDataSheetView(DataSheetView dataSheetView) { this.dataSheetView = dataSheetView; }
        // MinMaxView를 설정함
        public void setMinMaxView(MinMaxView minMaxView) { this.minMaxView = minMaxView; }
        // 새로운 점수를 추가하면 출력하는 것에 변화를 통보(update())하여 출력하는 부분 갱신
        public void addScore(int score) {
       scores.add(score); // scores 목록에 주어진 점수를 추가함
       dataSheetView.update(); // scores가 변경됨을 통보함
       minMaxView.update(); // scores가 변경됨을 통보함
        }
        // 출력하는 부분에서 변화된 내용을 얻어감
        public List<Integer> getScoreRecord() {
       return scores;
        }
      }
      
      public class Client {
        public static void main(String[] args) {
         ScoreRecord scoreRecord = new ScoreRecord();
         // 3개까지의 점수만 출력함
         DataSheetView dataSheetView = new DataSheetView(scoreRecord, 3);
         // 최대값, 최소값만 출력함
         MinMaxView minMaxView = new MinMaxView(scoreRecord);
         // 각 통보 대상 클래스를 저장
         scoreRecord.setDataSheetView(dataSheetView);
         scoreRecord.setMinMaxView(minMaxView);
         // 10 20 30 40 50을 추가
         for (int index = 1; index <= 5; index++) {
           int score = index * 10;
           System.out.println("Adding " + score);
           // 추가할 때마다 최대 3개의 점수 목록과 최대/최소값이 출력됨
           scoreRecord.addScore(score);
         }
        }
      }
      
    • 이 경우에도 점수 변경에 대한 통보 대상 클래스가 다른 대상 클래스(DataSheetView->MinMaxView)로 바뀌면 기존 코드(ScoreRecord 클래스)의 내용을 수정해야 하므로 OCP에 위배된다.
    • 즉, 성적 변경을 새로운 클래스에 통보할 때마다 ScoreRecord 클래스의 코드를 수정해야 하므로 재사용하기 어렵다.

해결책

문제를 해결하기 위해서는 공통 기능을 상위 클래스 및 인터페이스로 일반화 하고 이를 활용하여 통보하는 클래스(ScoreRecord 클래스)를 구현해야 한다.

  1. ScoreRecord 클래스의 addScore(상태 변경) 메서드 호출
    • 1-1. 자신의 성적 값 저장
    • 1-2. 상태가 변경될 때마다 Subject 클래스의 notifyObservers 메서드 호출
  2. Subject 클래스의 notifyObservers 메서드 호출
    • Observer 인터페이스를 통해 성적 변경을 통보
    • 2-1. DataSheetView 클래스의 update 메서드 호출
    • 2-2. MinMaxView 클래스에 update 메서드 호출

관련된 Post

References