[디자인 패턴] 옵저버 패턴

이정규·2022년 6월 15일
0

정의

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의한다.

작동 원리

신문사와 구독자를 생각해보자. 신문사는 구독자가 되면 구독자에게만 신문을 전달해준다.

만일 구독하지 않았다면 신문을 전달해주지 않는다.

그래서 신문을 보고 싶다면 신문사에게 구독 요청을 진행하고 이를 승인받으면 구독자가 되고 신문을 받을 수 있게 되는 것이다.

예시

기상 데이터를 받아오는 객체가 존재하고, 기상 데이터가 변화될 때 마다 다른 디스플레이에 뿌려준다라고 생각해보자.

일반적으로 짜면 이렇게 될 것이다.

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);
    }

해당 메소드의 문제점

  1. 추후에 새로운 디스플레이가 나오면 메소드를 변경해야한다는 점이다.

  2. 온도, 습도, 기압만을 받고 있는데 추후에 다른 데이터를 추가하고 싶다면 메소드 변경이 이루어져야 한다.

  3. 너무 구체적이다.

    데이터를 그저 넘겨주기만 하면 되는데 해당 디스플레이가 무엇인지 알 필요가 없다. 즉, 결합도가 높아졌다는 뜻이고 이를 추상화해서 결합도를 떨어뜨려보자.

옵저버 패턴 적용

일단 WeatherData라는 부분은 “신문사" 라고 생각해보고 옵저버에게 알려주는 주체라고 생각해보자.

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}
public class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

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

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

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

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

    public void measurementsChanged() {
        notifyObservers();
    }

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

이렇게 만들어보자.

그러면 다른 옵저버 패턴의 주체가 생겼을 때에도 subject 인터페이스를 상속받아 진행하면 된다.

이제 옵저버(위에서 봤던 디스플레이들)를 만들어보자.

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

우리는 디스플레이가 여러개 존재하니 이도 추상화시켜보자.

public interface DisplayElement {
    void display();
}

자 이제 구현체를 만들어보자.

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

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

    @Override
    public void display() {
        System.out.println(temperature + " " + humidity);
    }

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

옵저버가 되고 싶다면 해당 WeatherData를 넣어 옵저버에 등록하기만 하면 된다.

그 후에는 그냥 WeatherData라는 주체가 알아서 해줄 것이다.

public class Main {
    public static void main(String[] args) {
        WeatherData weather = new WeatherData();

				CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weather);

        weather.setMeasurements(36.5f, 10f, 1f);
        weather.setMeasurements(36f, 15f, 2f);
    }
}

잘 작동하는 것을 볼 수 있다. WeatherData객체를 변경했을 뿐인데 바로 display에서 변경을 감지하고 나타낸다.

자바에도 Observer인터페이스가 존재하던데요..?

옵저버 패턴을 많이 사용하길래 Java에서 직접 추가해준 것이다. 이를 사용해도 간단한 옵저버 패턴을 구현할 수 있다.

하지만, 커스터마이징하기가 불편하고 더 강력한 기능들을 원했기에 Java 9 이후로 쓰이지 않는다.

옵저버의 알림 순서를 정해야 하나요?

알림 순서에 의존하지 말라는 JDK권고가 있다.

지금보니깐 WeatherData객체에서 데이터를 넘겨주는 방식이 아닌 CurrentConditionDisplay객체에서 데이터를 가져오는 방식은 어때요?

이는 data를 push해줄 것이냐, 아니면 pull해올 것이냐에 차이다.

구현 방법에 차이다. 어느 것을 사용하든 상관이 없다. 만약 pull해오고 싶다면 WeatherData객체에서 getter 메소드를 구현하고 Observer 구현체에서 get메소드를 호출해서 진행하면 된다.

참고
헤드퍼스트 디자인 패턴

profile
강한 백엔드 개발자가 되기 위한 여정

0개의 댓글