옵저버 패턴(Observer Pattern)

wannabeking·2022년 10월 4일
0

디자인 패턴

목록 보기
3/14
post-thumbnail

해당 예시는 헤드 퍼스트 디자인 패턴을 참고했습니다.

기상 스테이션 예제

온도, 습도, 기압 센서를 통해 기상을 관측하는 기상 스테이션이 있다.

우리는 기상 스테이션의 관측 결과를 WeatherData 객체에 저장할 것이다.

또한 여러 디스플레이 장비가 존재하는데, WeatherData의 값이 변할때마다 출력한다.

자! 이제 우리는 이 것을 구현해야 한다.
상상만해도 복잡하고 어떻게 구현해야할지 감이 잡히지 않는다.

이럴때는 선조의 지혜(디자인 패턴)을 빌려야한다! 옵저버 패턴을 사용하여 해결해보자.



옵저버 패턴

옵저버 패턴이란 주제에서 어떠한 이벤트가 발생하면 주제를 구독하고 있는 옵저버들이 특정 행동을 취하는 것이다.

이때 주제와 옵저버들은 느슨한 결합을 사용하고 있기 때문에 유연성이 좋다.

느슨한 결합 : 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계를 의미한다.

그럼 이제 옵저버 패턴을 사용하여 위의 예제를 구현해보자.


public interface Subject {

    void registerObserver(Observer observer);

    void removeObserver(Observer observer);

    void notifyObservers();
}
public interface Observer {

    void update();
}
public interface DisplayElement {

    void display();
}

사용할 인터페이스는 위와 같다.

Subject는 주제를 의미한다.
주제란 옵저버가 구독하는 것이라고 생각하면 된다. 위의 예제에선 WeatherData가 될 것이다.

Observer는 주제를 구독하며 하나의 주제에 여러 옵저버가 존재한다. (one to many)
만약 주제에서 변화가 생긴다면 옵저버는 무언가 행동을 취할 것이다.

DisplayElement는 예제의 디스플레이 장비에 해당된다.

가장 중요한 Subject를 먼저 예제에 맞춰 구현해보자.


public class WeatherData implements Subject {

    private final List<Observer> observers;
    private double temperature;
    private double humidity;
    private double pressure;

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

    public double getTemperature() {
        return temperature;
    }

    public double getHumidity() {
        return humidity;
    }

    public double getPressure() {
        return pressure;
    }

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

    public void measurementsChanged() {
        notifyObservers();
    }

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

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

    @Override
    public void notifyObservers() {
        observers.forEach(Observer::update);
    }
}

observers는 현재 기상 정보를 구독하고 있는 옵저버의 리스트이다.
메소드를 통해 옵저버를 등록하거나 삭제할 수 있다.

만약 관측 정보가 바뀐다면(setMeasurements()) 구독하고 있는(?) 옵저버들에게 알린다.
notifyObservers()를 확인하면 구독하고 있는 모든 옵저버들의 update()를 실행시켜 알리는 것을 확인할 수 있다.

다음으로 옵저버를 구현해보자.


public class DetailDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;
    private double temperature;
    private double humidity;
    private double pressure;

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

    @Override
    public void update() {
        temperature = weatherData.getTemperature();
        humidity = weatherData.getHumidity();
        pressure = weatherData.getPressure();
        display();
    }

    @Override
    public void display() {
        System.out.println("온도 : " + temperature + "F, 습도 : " + humidity + "%, 기압 : " + pressure);
    }
}

DetailDisplay는 모든 관측 정보를 출력한다.

update()가 수행되면 구성(Composition)하고 있는 WeatherData의 getter를 호출하여 this를 update하고 출력한다.

온도만 출력하거나 온도, 습도만 출력하는 등 다양한 디스플레이 장치를 구현할 수도 있다.

그럼 다음으로 psvm과 출력 값을 확인해보자.


public static void main(String[] args) {
    WeatherData weatherData = new WeatherData();
    
    DailyDisplay dailyDisplay = new DailyDisplay(weatherData);
    weatherData.setMeasurements(80.0d, 65.0d, 1020.8d);
    weatherData.removeObserver(dailyDisplay);
    weatherData.setMeasurements(79.0d, 64.0d, 1019.8d);
    
    DetailDisplay detailDisplay = new DetailDisplay(weatherData);
    weatherData.setMeasurements(78.0d, 63.0d, 1018.8d);
}

필자는 온도, 습도만 출력하는 DailyDisplay와 온도, 습도, 기압을 모두 출력하는 DetailDisplay 두 개를 구현했다.

dailyDisplay(옵저버)는 WeatherData(주제)가 한번 수정된 뒤 구독을 취소하고 옵저버 리스트에서 빠져나왔기 때문에 다음 setMeasurements()에서는 출력되지 않을 것이다.

detailDisplay는 기압까지 출력하는 디스플레이 장치이므로 관측된 모든 기상이 출력될 것이다.

실행 결과는 다음과 같다.


모든 소스코드는 여기에서 확인할 수 있다.



profile
내일은 개발왕 😎

0개의 댓글