Design pattern

[Design Pattern] Observer Pattern

s00ng 2022. 8. 4. 22:15
1. 옵저버 패턴이란?
데이터의 변경이 발생했을 경우,그와 연관된 객체들에게 알림을 보내는 디자인 패턴
  • 상대 클래스나 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 유용
  • 통보 대상 객체 관리를 Subject 클래스와 Observer 인터페이스로 일반화
  • 통보 대상 클래스나 대상 객체의 변경에도 ConcreteSubject 클래스를 수정 없이 그대로 사용할 수 있도록 함

 

옵저버패턴 콜라보레이션

Observer: 데이터의 변경을 통보 받는 인터페이스. Subject에서는 Observer 인터페이스의 update 메서드를 호출함으로써 ConcreteSubject의 데이터 변경을 ConcreteObserver에게 통보

 

Subject: ConcreteObserver 객체를 관리하는 요소. Observer 인터페이스를 참조해서 Concrete Observer를 관리하므로 ConcreteObserver의 변화에 독립적

 

ConcreteSubject: 변경 관리 대상이 되는 데이터가 있는 클래스. 데이터의 변경을 위한 메서드인 setState가 있으며 setState 에서는 자신의 데이터인 subjectState를 변경하고 Subject의 notifyObservers 메서드를 호출해서 ConcreteObserver 객체에 변경을 통보

 

ConcreteObserver: ConcreteSubject의 변경을 통보받는 클래스. Observer 인터페이스의 update 메서드를 구현함으로써 변경을 통보 받음. 변경된 데이터는 ConcreteSubject의 getState 메서드를 호출함으로써 변경을 조회

 

2. 옵서버 패턴 예시
/* 옵저버 패턴 사용하지 않은 코드*/

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);
        dataSheetView.update();
    }
    
    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> recore = 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();
        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);
        	
            scoreRecord.addScore(score);
        }
    }
}

 

문제점

  • 성적을 다른 형태로 출력하는 경우   현재 점수가 입력되었을 때 지정된 특정 대상 클래스에게 고정적으로 통보하도록 코딩이 되어있으므로, 다른 대상 클래스에게 점수가 입력되었음을 통보하려면 ScoreRecord 클래스의 변경이 필요 (OCP 위반)
  • 동시 혹은 순차적으로 성적을 출력하는 경우 → 이 역시 성적의 통보 대상이 변경된 것을 반영하기 위해 ScoreRecord 클래스의 코드를 수정해야함(OCP 위반)
/* 옵저버 패턴 사용 코드 */

public interface Observer {
	public abstract 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); // 통보 대상 제거
    }
    
    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> recore = 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 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);
    }

}