// 새로운 점수를 추가하면 출력하는 것에 변화를 통보(update())하여 출력하는 부분 갱신. publicvoidaddScore(int score){ scores.add(score); // scores 목록에 주어진 점수를 추가한다. dataSheetView.update(); }
// 출력하는 부분에서 변화된 내용을 얻어감. public List<Integer> getScoreRecord(){ return scores; } }
// 점수의 변경을 통보 받는다. publicvoidupdate(){ List<Integer> record = scoreRecord.getScoreRecord(); // 점수를 조회한다. displayScores(record, viewCount); // 조회된 점수를 viewCount 만큼만 출력한다. }
// 점수를 출력한다. privatevoiddisplayScores(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(); } }
ObserverClient 클래스
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
publicclassObserverClient{ publicstaticvoidmain(String[] args){ ScoreRecord scoreRecord = new ScoreRecord(); // 3개까지의 점수만 출력. DataSheetView dataSheetView = new DataSheetView(scoreRecord, 3); scoreRecord.setDataSheetView(dataSheetView);
for (int i = 1; i <= 5; i++) { int score = i * 10; System.out.println("Adding : " + score); // 10, 20, 30, 40, 50을 추가한다. // 추가할 때마다 최대 3개의 점수만 출력한다. scoreRecord.addScore(score); } } }
ScoreRecord 클래스의 addScore() 메소드가 호출되면 ScoreRecord 클래스는 자신의 필드인 scores 객체에 점수를 추가한다.
그리고 DataSheetView 클래스의 update() 메소드를 호출함으로써 성적을 출력하도록 요청한다.
DataSheetView 클래스는 ScoreRecord 클래스의 getScoreRecord() 메소드를 호출해 출력할 점수를 구한다.
이때 DataSheetView 클래스의 update() 메소드에서는 구한 점수 중에서 명시된 개수만큼(viewCount)의 점수만 출력한다.
문제점
성적을 다른 형태로 출력하는 경우
성적을 목록으로 출력하지 않고 성적의 최소, 최대 값만 출력하려면?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
publicclassScoreRecord{ private List<Integer> scores = new ArrayList<>(); private MinMaxView minMaxView;
// 새로운 점수를 추가하면 출력하는 것에 변화를 통보(update())하여 출력하는 부분을 갱신한다. publicvoidaddScore(int score){ scores.add(score); dataSheetView.update(); // scores가 변경됨을 통보한다. minMaxView.update(); // scores가 변경됨을 통보한다. }
public List<Integer> getScoreRecord(){ return scores; } }
이 경우에도 점수 변경에 대한 통보 대상 클래스가 다른 클래스로 바뀌면(DataSheetView -> MinMaxView) 기존 코드의 내용을 수정해야 하므로 OCP에 위배된다.
즉, 성적 변경을 새로운 클래스에 통보할 때마다 ScoreRecord 클래스의 코드를 수정해야 하므로 번거롭고 재사용하기가 어렵다.
위의 그림처럼 양방향의 의존 관계를 가지고 있기 때문에 통보 대상 클래스가 변경되면 ScoreRecord 클래스도 수정해야 한다. 따라서 우리는 이 문제를 해결해야 한다.
해결책
문제를 해결하기 위해서는 공통 기능을 상위 클래스 및 인터페이스로 일반화하고 이를 활용하여 통보하는 클래스를 구현해야 한다.
즉, ScoreRecord 클래스에서 변화되는 부분을 식별하고 이를 일반화시켜야 한다.
이를 통해 성적 통보 대상이 변경되더라도 ScoreRecord 클래스를 그대로 재사용할 수 있다.
/** * 추상화된 변경 관심 데이터 * 즉, 데이터에 공통적으로 들어가야 하는 메소드들 -> 일반화 */ publicabstractclassSubject{
// 추상화된 통보 대상 목록. // 즉, 출력 형태에 대한 Observer private List<Observer> observers = new ArrayList<Observer>();
// 통보 대상 추가. publicvoidattach(Observer observer){ observers.add(observer); }
// 통보 대상 제거. publicvoiddetach(Observer observer){ observers.remove(observer); }
// 각 통보 대상에게 변경을 통보한다. publicvoidnotifyObservers(){ for (Observer observer : observers) { observer.update(); } } }
ScoreRecord 클래스
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/** * 구체적인 감시 대상 데이터 * 출력 형태 2개를 가질 때 */ publicclassScoreRecordextendsSubject{ private List<Integer> scores = new ArrayList<>();
// 새로운 점수를 추가. -> 상태 변경 publicvoidaddScore(int score){ scores.add(score); // scores 목록에 주어진 점수를 추가한다. notifyObservers(); // scores 가 변경됨을 각 통보 대상에게 통보한다. }
public List<Integer> getScoreRecord(){ return scores; } }
// 점수를 출력한다. privatevoiddisplayScores(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(); }
// 점수의 변경을 통보 받는다. @Override publicvoidupdate(){ List<Integer> record = scoreRecord.getScoreRecord(); // 점수를 조회한다. displayScores(record, viewCount); // 조회된 점수를 viewCount 만큼만 출력한다. } }
privatevoiddisplayScores(List<Integer> record){ int min = Collections.min(record); int max = Collections.max(record); System.out.println("Min : " + min + ", Max : " + max); }
@Override publicvoidupdate(){ List<Integer> record = scoreRecord.getScoreRecord(); displayScores(record); }
publicclassObserverClient{ publicstaticvoidmain(String[] args){ ScoreRecord scoreRecord = new ScoreRecord();
// 3개까지만 데이터를 출력한다. DataSheetView dataSheetView = new DataSheetView(scoreRecord, 3); // 최대, 최소 값만 출력한다. MinMaxView minMaxView = new MinMaxView(scoreRecord);
// 각 통보 대상 클래스를 Observer 로 추가한다. scoreRecord.attach(dataSheetView); scoreRecord.attach(minMaxView);
for (int i = 1; i <= 5; i++) { int score = i * 10; System.out.println(); System.out.println("Adding : " + score); scoreRecord.addScore(score); } } }
Observer : 추상화된 통보 대상
DataSheetView, MinMaxView : Observer를 구현함으로써 구체적인 통보 대상이 된다.
Subject : 성적 변경에 관심이 있는 대상 객체들을 관리한다.
ScoreRecord : Subject를 상속받음으로써 구체적인 통보 대상을 직접 참조하지 않아도 된다. notifyObservers()를 호출함으로써 Subject 클래스에서 각 통보 대상들에게 변경 사항을 통보한다.
이렇게 Observer 패턴을 이용하면 ScoreRecord 클래스의 코드를 변경하지 않고도 새로운 관심 클래스 및 객체를 추가/제거하는 것이 가능해진다.
느낀 점
결국, 처음에는 양방향으로 참조를 해서 강한 의존성을 갖고 있었다. 하지만, Observer 패턴을 적용함으로써 의존성을 약하게 결합하도록 하고 양방향 참조를 하지 않도록 함으로써 디자인 원칙을 위배하지 않으면서 데이터 변경을 적절하게 통보할 수 있게 되었다.
이처럼 느슨하게 결합하는 디자인을 사용하면 변경 사항이 생겨도 무난하게 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있다. (객체 사이의 상호 의존성을 최소화 할 수 있기 때문이다.)