Design Pattern

옵저버 패턴(Observer Pattern)

칼퇴시켜주세요 2022. 3. 14. 19:54
728x90

옵저버 패턴이란?

옵저버 패턴은 신문사와 정기구독자로 이루어지는 신문 구독 서비스에 비유해서 생각하면 됩니다. 즉 옵저버 패턴에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신이되는 방식으로 일대다(one-to-many)의존성을 정의합니다.

 

HeadFirst Design Pattern의 예제를 보면 다음과 같이 WeatherData 객체의 상태가 바뀔때 마다 n개의 디스플레이에 업데이트를 시켜주어야 하는 상황을 확인할 수 있습니다.

public class WeatherData {
    private float temperature;

    private float humidity;

    private float pressure;

    public WeatherData(){}

    public WeatherData(float temperature, float humidity, float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure= pressure;
    }

    public float getTemperature(){
        return this.temperature;
    }

    public float getHumidity() {
        return this.humidity;
    }

    public float getPressure() {
        return this.pressure;
    }

    /*
        기상 관측값이 갱신될 때마다 여러개의 디스플레이에 알려주기 위한 메소드
     */
    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);
    }
}

위의 코드에서 measurementsChanged()메서드에서 update() 중복 코드가 발생하는 것을 확인 할 수 있고 이는 두 객체 사이에 강한 결합을 형성하고 의존성이 증가합니다. 이를 해결하기 위해 옵저버 패턴을 적용하여 강한 결합을 느슨한 결합으로 바꾸어 표현할 수 있습니다.

클래스 다이어그램

옵저버 패턴의 클래스 다이어그램은 다음과 같이 나타낼 수 있습니다.

옵저버 패턴 다이어그램


커스텀 옵저버 패턴 구현

옵저버 패터는 구현하기 어렵지 않아서 아래 처럼 직접 구현해도 됩니다. 하지만 자바에서 몇가지 API를 통해 자체적으로 옵저버 패턴을 지원합니다. 내장 옵저버는 조금 있다가 알아보도록 하겠습니다.

 

Custom Observer Interface

package Observer;

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

 

Custom Subject Interface

package Subject;

import Observer.Observer;

public interface Subject {
    public void registerObserver(Observer o);

    public void removeObserver(Observer o);

    public void notifyObserve();
}

 

Custom Subject Class

import Observer.Observer;
import Subject.Subject;

import java.util.ArrayList;

public class WeatherData implements Subject {
    private final ArrayList<Observer> observers;

    private float temperature;

    private float humidity;

    private float pressure;

    public WeatherData(){
        this.observers = new ArrayList();
    }

    @Override
    public void registerObserver(Observer o) {
        this.observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i= observers.indexOf(o);

        if(i>0){
            this.observers.remove(i);
        }
    }

    @Override
    public void notifyObserve() {
        for(int i=0;i<observers.size();i++){
            Observer observer = this.observers.get(i);
            observer.update(this.temperature, this.humidity, this.pressure);
        }
    }

    public void measurementsChanged(){
        notifyObserve();
    }

    public void setMeasurements(float temperature, float humidity, float pressure){
        this.temperature= temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }
}

 

Custom Observer Class

import DisplayElement.DisplayElement;
import Observer.Observer;
import Subject.Subject;

public class CurrentConditionDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;

    public CurrentConditionDisplay(Subject weatherData){
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature= temp;
        this.humidity= humidity;
        display();
    }

    @Override
    public void display() {
        System.out.println("Current conditions: "+ this.temperature + "F degrees and " + this.humidity + "% humidity");
    }
}

자바 내장 옵저버

자바에 이렇게 자주 사용하는 옵저버 패턴을 위해 내장 API가 존재합니다. 위에서 구현한 커스텀 옵저버 코드를 보면 Subject에서 온도,기온 등... 이 바꼈을 경우 모든 옵저버들에게 업데이트를 호출 합니다. 하지만 만약 온도나 기온등이 실시간으로 바뀐다고 가정해본다면 Sudject는 끊임없이 연락을 돌려야 합니다. 

 

자바 내장 API인 Observable은 setChanged 메소드를 제공하여 조건에 따라 옵저버에게 업데이트를 명령할 수 있습니다. 하지만 단점은 Observable은 인터페이스가 아닌 클래스인 데다가, 어떤 인터페이스를 구현한 것도 아닙니다. 따라서 안타깝게도 재사용성에 몇가지 제약이 발생하고 주의해야 할 점들이 있습니다.

  • Observable이 클래스기 때문에 서브클래스를 만들어야 한다는 점이 문제가 됩니다. 이미 다른 수퍼클래스를 확장하고 있는 클래스에는 Observable의 기능을 추가 할 수 없습니다.(자바 클래스는 다중 상속 안됨 -> 다이아몬드 상속 충돌 문제)
  • Observable 인터페이스라는 것이 없기 때문에 자바에 내장된 ObserverAPI하고 잘 맞는 클래스를 직접 구현하는 것이 불가능 합니다. (예를 들어 멀티스레드로 구현한다거나 하는 일이 아예 불가능함)
  • Observable API를 살펴보면 setChanged() 메소드가 protected로 선언되어 있음을 알 수 있습니다 즉 서브 클래스에서만 호출 가능합니다. 

위와 같은 제약 사항이 존재하기 때문에 제일 처음 했던 방식으로 Custom Observer를 구현하는것이 좋을수 있습니다.

 

 

Observable SubClass

import java.util.Observable;

public class WeatherData extends Observable {
    private float temperature;

    private float humidity;

    private float pressure;

    public WeatherData(){ }

    public void measurementsChanged(){
        setChanged();
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure){
        this.temperature= temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

 

Observer Class

import DisplayElement.DisplayElement;

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

public class CurrentConditionDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Observable observable;

    public CurrentConditionDisplay(Observable observable){
        this.observable =observable;
        observable.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current conditions: "+ this.temperature + "F degrees and " + this.humidity + "% humidity");
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData){
            WeatherData weatherData = (WeatherData)o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
}

 

반응형