mojo's Blog

Observer Pattern 본문

Design Patterns

Observer Pattern

_mojo_ 2024. 6. 6. 13:57

Observer Pattern

 

목적: 시스템 내 다른 객체의 상태 변경을 하나 이상의 객체에 알리기 위함

사용 시기

- 커뮤니케이션을 위해 느슨한 커플링이 필요함

- 하나 이상의 객체에서 상태 변경이 다른 객체에서 동작을 트리거링 필요

- 브로드캐스팅 기능이 필요함

 

※ Application Overview

 

 

현재 상태 디스플레이는 위와 같이 사용자는 몬스터의 체력, 위치 정보를 시각화하여 볼 수 있다.

다른 디스플레이는 몬스터의 체력, 위치 정보를 수치화하여 볼 수 있고,

또 다른 디스플레이는 몬스터의 체력, 위치 정보를 백분율로 볼 수 있다.

MonsterData 객체를 이용하여 어플리케이션을 만들어 위와 같이 3 개의 display 를 업데이트 해야 한다.

 

 

몬스터 데이터를 사용하는 요소들은 아래와 같다.

- 현재 몬스터 체력 표시

- 현재 몬스터 위치 표시

 

MonsterData 가 새로운 측정값을 가질 때마다 업데이트해야 한다.

시스템을 확장할 수 있어야 한다.

- 개발자는 새로운 사용자 정의 디스플레이 요소를 만들 수 있어야 함 

- 사용자는 원하는 만큼 어플리케이션에 디스플레이 요소를 추가하거나 제거할 수 있어야 함

 

※ measurementsChanged 에 대한 구현

 

public class MonsterData {
	// instance variable declarations

	public void measurementsChanged() {
    	int stamina = getStamina();
        Point location = getLocation();
        
        currentConditionDisplay.update(stamina, location);
        digitizationDisplay.update(stamina, location);
        percentageDisplay.update(stamina, location);
    }
    
    // other MonsterData methods here
}

 

위와 같이 구체적인 구현에 대한 코딩을 함으로 인해서,

프로그램을 변경하지 않고 다른 display 요소를 추가하거나 제거할 방법이 없다. 

 

※ The Observer Pattern Defined

객체 간의 일대일 종속성을 정의하여 하나의 객체가 상태를 변경할 때,

해당 객체의 종속성을 모두 자동으로 알리고 업데이트 한다.

 

 

Observers는 의존성 객체들로 데이터가 변경될 때 업데이트할 Subject에 의존한다.

Observer Pattern에 대한 클래스 다이어그램은 위와 같다.

대략적으로 Subject는 NotifyObservers 메서드를 통해 변경된 데이터를 Observer 에게 알리는 구조이다.

 

※ The Power of Loose Coupling

상호 작용하는 객체간 느슨하게 결합된 설계를 위해 노력해야 한다.

두 객체가 느슨하게 결합되면 상호 작용할 수 있지만, 서로에 대한 정보가 거의 없다.

Observer pattern은 subject와 observers가 느슨하게 결합된 객체 설계를 제공한다.

 

Observer Pattern에 대한 클래스 다이어그램을 참고하여,

Monster Station 에 대한 설계를 하면 다음과 같다.

 

 

※ Interface 코드 

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}
public interface Observer {
	public void update(int stamina, Point location);
}
public interface DisplayElement {
	public void display();
}

 

※ Subject 를 구현한 MonsterData 코드

public class MonsterData implements Subject {
	private ArrayList observers;
    private int stamina;
    private Point location;
    
    public MonsterData() {
    	observers = new ArrayList();
    }
    public void registerObserver(Observer o) {
    	observers.add(o);
    }
    public void removeObserver(Observer o) {
    	int idx = observers.indexof(o);
    	if (idx >= 0)
        	observers.remove(idx);
    }
    public void notifyObservers() {
    	for (int i = 0; i < observers.size(); i++) {
        	Observer observer = (Observer)observers.get(i);
            observer.update(stamina, location);
        }
    }
    public void measurementsChanged() {
    	notifyObservers();
    }
    public void setMeasurements(int stamina, Point location) {
    	this.stamina = stamina;
        this.location = location;
        measurementsChanged();
    }
    // other MonsterData methods here
}

 

몬스터를 디바이스를 통해 보고 있는 유저들은 ArrayList인 observers 을 통해 위와 같이 관리한다.

몬스터의 체력이나 위치가 변경될 경우, 값을 업데이트하면서 유저들에게 체력, 위치를 업데이트 시켜서

디바이스를 통해 보고 있는 몬스터의 체력이나 위치가 조정되어 보여질 수 있도록 한다.

 

※ Observer, DisplayElement 를 구현한 CurrentConditionDisplay 코드

public class CurrentConditionDisplay implements Observer, DisplayElement {
	private int stamina;
    private Point location;
    private Subject monsterData;
    
    public CurrentConditionDisplay(Subject monsterData) {
    	this.monsterData = monsterData;
        monsterData.registerObserver(this);
    }
    public void update(int stamina, Point location) {
    	this.stamina = stamina;
        this.location = location;
        display();
    }
    public void display() {
    	// display current condition in device
    }
}

 

디바이스를 통해 현재 몬스터의 체력, 위치를 볼 수 있는 클래스이다.

Subject인 monsterData에 자기 자신의 객체를 observer로 등록함으로써,

체력이나 위치가 변경될 때마다 update 가 호출되어 새로운 상태의 몬스터가 디바이스에 보여지게 된다.

 

※ Monster Station 에 대한 코드 

public class MonsterStation {
	public static void main(String[] args) {
    	MonsterData monsterData = new MonsterData();
        
        CurrentConditionDisplay currentDisplay = 
        		new CurrentConditionDisplay(monsterData);
        DigitizationDisplay digitizationDisplay = 
        		new DigitizationDisplay(monsterData);
        PercentageDisplay percentageDisplay = 
        		new PercentageDisplay(monsterData);
                
        monsterData.setMeasurements(100, new Point(100, 100));
        monsterData.setMeasurements(90, new Point(120, 100));
        monsterData.setMeasurements(70, new Point(120, 140));
    }
}

 

위 코드를 정리하면,

(1) 디바이스를 통해 몬스터를 나타내는 currentDisplay

(2) 몬스터의 체력, 위치 값을 나타내는 digitizationDisplay

(3) 몬스터의 체력, 위치를 백분율로 나타내는 percentDisplay

위 3개의 display가 각각 subject인 monsterData를 참조하는 상태가 된다.

 

체력, 위치가 변경될 때마다 이뤄지는 작업은 다음과 같다.

(1) 현재 monster의 체력, 위치를 업데이트

(2) 3 개의 observer에 update 메서드 호출

(3) observer가 가지고 있는 monster의 체력, 위치를 새로 업데이트 해주고 display 메서드 호출

 

※ Loose coupling 달성

- 두 객체가 서로에 대해 거의 알지 못하지만, 상호 작용할 수 있는 경우 느슨하게 결합된 것으로 간주

- Observer pattern은 loose coupling의 좋은 예시임

- Subject가 observer에 대해 아는 것은 특정 인터페이스를 구현한다는 것 뿐임

- 이는 subject 또는 observer 중 한 명의 변경 사항이 다른 사람에게 영향을 미치지 않기 때문에 좋음

- Subject를 변경하지 않으면서 언제든 새로운 유형의 observer를 추가할 수 있음

 

'Design Patterns' 카테고리의 다른 글

State Pattern  (0) 2024.06.06
Template Method Pattern  (0) 2024.06.06
Strategy Pattern  (0) 2024.06.06
GRASP  (1) 2024.05.15
SOLID 원칙  (0) 2024.05.06
Comments