2013년 7월 9일 화요일

옵저버 패턴

옵저버 패턴에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고
자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의합니다.
신문 구독으로 간단한 예를들 수 있다.
신문사는 새로운 신문을 발행한다. 신문을 구독 중인 고객들에게는 새 신문이 배달되고, 고객들은 자동으로 구독해지, 신청을 언제든지 할 수 있다.
신문사는 구독 중인 고객들에게 신문을 배달하는 순서는 신경쓰지 않는다.

간단한 기상 스테이션을 Observer 패턴으로 구현해보자.
다음과 같이 subject, observer 인터페이스를 만들어 간단히 구현 가능하다.

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

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

public interface DisplayElement {
    public void display();
}
신문사에 해당하는 Subject 인터페이스와 고객인 Observer 인터페이스, 기상 정보를 표현할 DisplayElement 인터페이스로 구성되어 있다.
public class WeatherData implements Subject {
    private final ArrayList<Observer> observers;
    private float pressure;
    private float humidity;
    private float temperature;

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

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

    private void measurementsChanged() {
        notifyObservers();
    }

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

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for(Observer o : observers) {
            o.update(this.temperature, this.humidity, this.pressure);
        }
    }
}

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

    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

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

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

WeatherData 클래스는 Observer들을 관리할 Observer 리스트를 가지고 있으며, registerObserver를 통해 등록된 Observer들에게 기상정보가 변경될 때 update메세지를 보낸다.

Observer인 CurrentConditionsDisplay 클래스는 weatherData의 registerObsever를 통해 Subject 클래스로 자신의 참조를 전달하고, Subject에서 update를 호줄하면 예정된 동작을 실행하게 된다.
현재 클래스에서는 생성자에서 register를 했으나 원할 때 remove, register 가능하다.

자바에서 Observer 패턴을 위한 기능이 있다.

java.util.Observable;
java.util.Observer;
위의 WeatherData와 CurrentConditionsDisplay를 아래와 같이 변경가능하다.
public class WeatherData extends Observable {
    private float pressure;
    private float humidity;
    private float temperature;

    public void setMeasurements(int temperature, int 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;
    }

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

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private final Observable weatherData;
    private float humidity;
    private float temperature;

    public CurrentConditionsDisplay(Observable weatherData) {
        this.weatherData = weatherData;
        weatherData.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();
        }
    }
}

주의할 점은 Observable이 인터페이스가 아니라 Class라는 점이다. 따라서 Observer를 위한 자료구조를 만들 필요가 없으며, addObserver/deleteObserver 등의 메소드가 이미 구현되어 있다.
implements가 아니라 extends해야 하기 때문에 사용에 제약이 있을 수 있다.
setChanged()를 호출한 후 notifyObservers()를 호출하면 등록된 Observer들의 update가 호출된다.

Observer들은 update(Observable, Object)를 통해 메세지를 받는다.
Observable은 update를 호출한 객체를 전달하고, Object에는 Observable에서 notifyObservers(Object)로 넘겨준 param이 전달된다.(notifyObservers()는 notifyObservers(null)과 같다.)
위 코드에서 처럼 Observable의 getter를 통해 데이터를 가져와도 되고(pull), notifyObservers(Object)로 넘겨줘도 된다.(push)


참고도서 :

댓글 없음: