본문 바로가기

기타/디자인패턴

[디자인패턴] Observer Pattern

1. 기상 모니터링 애플리케이션 개요

기상 모니터링 애플리케이션은 다음의 세 가지 요소로 이루어져 있고,

디스플레이 장비는 현재 기상 정보, 기상 통계, 기상 예보 항목을 표시한다.

 

  • 기상 스테이션(실제 기상 정보를 수집하는 장비)
  • WeatherData 객체(기상 스테이션으로부터 오는 데이터를 추적하는 객체)
  • 사용자에게 현재 기상 정보를 보여주는 디스플레이 장비

 

 

 

WeatherData 객체는 온도, 습도, 기압을 반환하는 메소드와

기상 관측값이 갱신될 때마다 알려주기 위한 메소드로 구성되어 있다.

 

measurementsChanged()를 현재 기상 정보, 기상 통계, 기상 예보의

3가지 디스플레이를 갱신할 수 있도록 구현해야 한다.

 

 

 

 

2. 기상 모니터링 임시 구현

public class WeatherData {
	
    // 인스턴스 변수 선언
    public void measurementsChanged() {
    
    	float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        
        // 현재 기상 정보, 기상 통계, 기상 예보 디스플레이 갱신
        currentConditionDisplay.update(temp, humidity, pressure);
        statisticsDisplay.update(temp, humidity, pressure);
        forecastDisplay.update(temp, humidity, pressure);
        
    }
	// 기타 메소드
}

 

# 문제점

  • 인터페이스가 아닌 구체적인 구현을 바탕으로 코딩을 하였다.
  • 실행 중에 디스플레이 항목을 추가/제거할 수 없다.
  • 바뀌는 부분을 캡슐화하지 않았다.

 

3. Observer Pattern 

Observer Pattern은 출판사와 구독자 사이의 메커니즘과 같다.

 

  • 출판사는 구독자에게만 데이터를 제공한다.
  • 출판 내용이 달라지면 구독자는 갱신 내용을 전달받는다.

Subject 객체에 등록된 Observer 객체만이 Subject로부터 데이터를 전달받는다.

 

 

 

 

 

 

일대다 관계?

- Observer Pattern에서 상태를 저장하고 지배하는 것은 Subject 객체

  따라서 상태가 들어있는 객체는 하나만 있을 수 있다.

 

- Observer는 반드시 상태를 가지고 있어야 하는 것이 아니기 때문에 여러 개의 객체가 있을 수 있다.

  또, Subject 객체에서 상태가 바뀌었다는 것을 알려주기를 기다리는 Subject에 의존적인 성질을 가진다.

 

- 따라서 하나의 Subject와 여러 개의 Observer가 연관된 일대다(one-to-many) 관계가 성립한다.

 

 

 

의존성?

- 데이터의 주인은 Subject(주제)

  Observer는 데이터가 변경되었을 때 Subject에서 갱신해주기를 기다리는 입장이기 때문에 의존성을 가짐

 

여러 객체에서 동일한 데이터를 제어하도록 하는 것에 비해 더 깔끔한 객체지향 디자인을 만들 수 있음

 

 

 

 

4. 느슨한 결합(Loose Coupling)

Observer Pattern은 Subject와 Observer가 느슨하게 결합되어 있는 객체 디자인을 제공.

...더보기

두 객체가 느슨하게 결합되어 있다는 것은, 그 둘이 상호작용을 하긴 하지만 서로에 대해 잘 모른다는 것을 의미

 

  • Subject가 Observer에 대해 아는 것은 Observer가 특정 인터페이스(Observer 인터페이스)를 구현한다는 것 뿐이며, Observer의 구상클래스가 무엇인지, Observer가 무엇을 하는지 등에 대해서는 알 필요가 없다.


  • Observer는 언제든지 새로 추가 및 삭제 할 수 있다.
    Subject는 Observer 인터페이스를 구현하는 객체의 목록에만 의존하기 때문에 언제든지 새로운 Observer를 추가할 수 있으며, 실행 중에 한 Observer를 다른 Observer로 바꿔도 Subject 객체는 계속해서 데이터를 보낼 수 있다.


  • 새로운 형식의 Observer를 추가하려고 할 때도 Subject를 전혀 변경할 필요가 없다.
    Observer가 되어야 하는 새로운 구상 클래스가 생겼다고 가정했을 때, 새로운 클래스 형식을 받아들일 수 있도록 Subject를 바꿔야 할 필요는 없다. 새로운 클래스에서 Observer 인터페이스를 구현하고 Observer로 등록하기만 하면 된다. Subject 객체는 전혀 신경 쓸 필요없이 Observer 인터페이스만 구현한다면 어떤 객체에든지 변경 내용이 전달되기 때문이다.


  • Subject와 Observer는 서로 독립적으로 재사용할 수 있다.
    느슨하게 결합되어 있기 때문에 Subject와 Observer를 다른 용도로 활용할 일이 있다고 해도 손쉽게 재사용할 수 있다.


  • Subject와 Observer가 바뀌더라도 서로에게 영향을 미치지 않는다.
    느슨하게 결합되어 있기 때문에 Subject 혹은 Observer 인터페이스를 구현한다는 조건만 만족된다면 어떻게 바꿔도 문제가 되지 않는다.

 

 


 

# 디자인 원칙 3

서로 상호작용하는 객체 사이에서는 가능한 느슨하게 결합하는 디자인을 사용해야 한다.

느슨하게 결합하는 디자인을 사용하면 변경사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있다.
객체 사이의 상호의존성을 최소화할 수 있기 때문이다.

 

 

 

 

5. 기상스테이션 구현(코드 예시)

 

- interface 선언

public interface Subject {
    public void registerObserver(Oberserver O);
    public void removeObserver(Observer O);
    public void notifyObserver(Observer O);
}

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
    public void display();
}

 

- Subject interface를 구현한 WeatherData class

public class WeatherData implements Subject {
    private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;
    
    public WeatherData() {
    	observers = new ArrayList();
    }
    
    public void registerObserver(Observer o) {
    	observers.add(o);
    }
    
    public void removeObserver(Observer o) {
    	int i = observers.indexOf(o);
        
        if(i >= 0) {
            observers.remove(i);
        }
    }
    
    public void notifyObservers() {
    	for(int i=0; i<observers.size(); i++) {
            Observer observer = (Observer)observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }
    
    public void measurementsChanged() {
    	notifiyObservers();
    }
    
    public void setMeasurements(float temperature, float humiditiy, float pressure) {
    	this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
    
    // 기타 WeatherData 메소드
}

 

- Display class

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;
    
    public CurrentConditionsDisplay(Subject weatherData) {
    	this.weatherData = weatherDatal
        weatherData.registerObserver(this);
    }
    
    public void update(float temperature, float humidity, float pressure) {
    	this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    
    public void display() {
    	System.out.println("Current conditions : " + temperature + "F degrees and " + humidity + "% humidity");
    }


}

 

- WeatherStation class (main)

public class WeatherStation {
    public static void main(String[] args) {
    	WeatherData weatherData = new WeatherData();
        
        CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(weatherData);
        //StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        //ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

 

 

 

6. push vs pull

  • push
    Subject가 Observer에 데이터를 보내는 방식  ex) notifyObservers(Object arg)

  • pull
    Observer가 필요한 데이터를 Subject로부터 가져가는 방식
    Subject의 내용을 드러내야 한다는 것과 필요한 상태를 모두 가져가기 위해 메소드를 여러 번 호출해야 하는 단점이 있다.

 

 

7. Java 내장 Observer Pattern 사용하기

* JDK9 버전부터는 더 이상 제공하지 않으며 java.util.concurrent.Flow API로 대체되었다.

 

  • 객체가 Observer가 되는 방법
    java.util.Observer 인터페이스를 구현하고 Observable 객체의 addObserver() 메소드를 호출한다.

  • Observable이 Observer에게 정보를 제공하는 방법
    (1) java.util.Observable 수퍼클래스를 확장하여 Observable 클래스를 만든다.
    (2) setChanged() 메소드를 호출해서 객체의 상태가 바뀌었다는 것을 알린다.
    (3) notifyObservers() 또는 notifyObservers(Object arg)를 호출한다.

  • Observer가 정보를 받는 방법
    update(Observable o, Object arg) 메소드를 호출한다.
    연락을 보내는 주제 객체(Observable)와 notifyObservers()메소드에서 인자로 전달된 데이터 객체(Object)를 인자로 가진다.

- java.util.Observable을 상속받은 WeatherData class

import java.util.Observable;
import java.util.Observer;

public class WeatherData extend Observable {
    private float temperature;
    private float humidity;
    private float pressure;
	
    public WeatherData() {}
    
    public void measurementsChanged() {
    	setChanged();
        notifyObservers();	// pull 방식
    }
    
    public void setMeasurements(float temperature, float humidity, float pressure) {
    	this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
    
    // pull 방식이기 때문에 getter 메소드 필요
    public float getTemperature() {
    	return temperature;
    }
    
    public float getHumidity() {
    	return humidity;
    }
    
    public float getPressure() {
    	return pressure;
    }
}

 

- java.util.Observer를 구현한 CurrentConditionDisplay

import java.util.Observable;
import java.util.Observer;

public class CurrentConditionsDisplay implements Observer {
    Observable observable;
    private float temperature;
    private float humidity;
	
    public CurrentConditionsDisplay(Observable observable) {
    	this.observable = observable;
        observable.addObserver(this);
    }
    
    public void update(Observable obs, Object arg) {
    	if(obs instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
    
    public void display() {
    	System.out.println("Current conditions : " + temperature + "F degrees and " + humidity + "% humidity");
    }
}

 

 

 

8. java.util.Observable의 단점

  • Observable은 클래스이다.
    이미 다른 수퍼클래스를 확장하고 있는 클래스에 Observable의 기능을 추가할 수 없다.  재사용성에 제약
    또한, 인터페이스가 아니기 때문에 내장된 Observer API하고 잘 맞는 클래스를 직접 구현하는 것이 불가능하다.

  • Observable 클래스의 핵심 메소드를 외부에서 호출할 수 없다.
    setChanged()와 같이 protected로 선언된 메소드를 가지는데 이는 Observable의 서브클래스에서만 호출할 수 있기 때문에 어떤 클래스를 만들고, Observable의 서브클래스를 인스턴스 변수로 사용하는 방법도 사용할 수 없다.
    디자인은 상속보다는 구성을 사용한다는 원칙에 위배됨

 

 

 

 

 

 

 

 


Observer Pattern

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들도 갱신되는 방식

객체들 사이의 일대다 관계를 정의

 


 

'기타 > 디자인패턴' 카테고리의 다른 글

[디자인패턴] Decorator Pattern  (0) 2019.10.15
[디자인패턴] Strategy Pattern  (0) 2019.09.17