본문 바로가기
디자인패턴

디자인패턴 - 옵저버 패턴 (Observer Pattern)

by 임동무 2023. 2. 6.

1. 옵저버 패턴이란

- 옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 

1대다 (one-to-many) 의존성을 정의한다.

 

- 디자인 원칙인 변경 가능성이 있는 부분과 그렇지 않은 부분을 분리하고, 구현보다는 인터페이스에 맞추어 프로그래밍하며, 상속보다는 구성을 활용하는 원칙을 준수한다.

 

- 추가로 느슨한 결합을 사용하여 프로그램의 유연성이 매우 좋아진다. 느슨한 결합이란, 객체들의 상호작용은 가능하지만, 서로에 대해서 잘 모르는 관계를 이야기한다. 즉 주제는 옵저버의 구상 클래스가 무엇인지, 어떤 역할을 하는지에 대해 알 필요가 없으며 주제나 옵저버가 달라져도 각 주제 혹은 옵저버를 구현한다는 조건만 만족하면 수정이 자유롭다.

 

 

구조/방식은 신문사의 구독 서비스와 동일하다. 이 서비스에 비유하며 설명하면

1. 주제(Subject) 와 옵저버 (Observer) 로 구성된다.

    -> 신문사의 구독 서비스에서 주제는 신문사, 옵저버는 구독자 이다.

 

2. 옵저버는 언제든지 등록 및 탈퇴가 가능하다.

   -> 사용자는 언제든지 구독 등록 및 탈퇴가 가능하다.

 

3. 주제에 변경사항이 있는 경우 모든 옵저버에게 연락이 간다. 

   -> 신문사에서 운영방침등에 대한 모든 변경사항이 있는 경우 구독자들에게 이를 공지한다.

 

 

기상 스테이션 서비스를 통해 옵저버 패턴을 알아보자

 

먼저 주제(Subject) 인터페이스이다.

public interface Subject {
    void registerObserver(Observer observer);    //옵저버 등록
    void removeObserver(Observer observer);      //옵저버 제거
    void notifyObserver();      //주제 내용 변경 알림
}

주제 인터페이스는

신규 옵저버를 등록하는 registerObserver 메서드,

기존 옵저버가 탈퇴하는 removeObserver 메서드,

주제 내용 변경을 옵저버에 알리는 notifyObserver 메서드로 이루어진다.

 

옵저버 패턴에서 데이터는 주제(Subject) 에서 관리한다.

즉, 기상스테이션의 기상 정보를 주제(Subject) 에서 관리해야한다. 

 

옵저버 패턴에서 주제와 옵저버가 소통하는 방식은 

주제에서 변경사항을 옵저버들에게 전달하는 push 방식과

옵저버에서 주제에게 정보를 가져오는 pull 방식이 있다.

 

주제 역할을 하는 즉 주제를 구현하는 클래스를 먼저 push 방식으로 정의해보자.

package observerpattern.subject;

import observerpattern.observer.Observer;

import java.util.ArrayList;
import java.util.List;

public class WeatherData implements Subject{
    private List<Observer> observerList;
    private float temperature;
    private float humidity;
    private float pressure;

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

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

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

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

    public void measurementsChanged() {
        notifyObserver();
    }

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

 

push 하는 방식에서는 데이터의 이동의 주체가 주제(Subject) 가 된다.

주제에서 변경사항이 있는 경우 옵저버들이 가진 상태들을 업데이트 해준다. 이 방식에서는 업데이트 시에 주제(Subject) 에 변경사항이 생기면 모든 값들을 옵저버의 업데이트 메서드의 파라미터로 전달하여 옵저버들의 상태를 업데이트 해준다.

 

그래서 Observer 를 보면 

// 옵저버의 인터페이스
package observerpattern.observer;

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

// 디스플레이 인터페이스
package observerpattern.observer;

public interface DisplayElement {
    void display();
}

// 구현체
package observerpattern.observer;

import observerpattern.subject.WeatherData;

public class CurrentConditionDisplay implements Observer, DisplayElement {
    private float temp;
    private float humidity;
    private WeatherData weatherData;

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

    @Override
    public void display() {
        System.out.println("현재 온도 : " + temp);
        System.out.println("현재 습도 : " + humidity);
    }

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

Observer 인터페이스에는 update 메서드가 정의되어 있고

모든 데이터를 update 의 파라미터로 전달한다. 그럼 해당 내용을 바탕으로

각 옵저버에서 필요한 데이터를 골라 상태를 변경한다.

여기서 Display 인터페이스는 화면에 출력하는 공통된 기능을 통합한 인터페이스이다.

 

push 방식에서의 문제점

위의 코드에서 보이듯이 이 방식에서 문제점은 위의 코드에서도 보이듯이 필요하지도 않은 모든 정보들이 옵저버에게 전달된다는 것이다.

위에서 pressure 라는 데이터는 사용되지 않지만 파라미터로 같이 넘어온다.

이런 문제를 해결하기 위해서 사용하는 방식이 pull 방식이다.

 

 

 

 

pull 방식의 코드를 보면

package observerpattern.subject;

import observerpattern.observer.Observer;

import java.util.ArrayList;
import java.util.List;

public class WeatherData implements Subject{
    private List<Observer> observerList;
    private float temperature;
    private float humidity;
    private float pressure;

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

    @Override
    public void registerObserver(Observer observer) {
        observerList.add(observer);     // 옵저버 추가
    }

    @Override
    public void removeObserver(Observer observer) {
        observerList.remove(observer);      // 옵저버 제거
    }

    @Override
    public void notifyObserver() {
        for (Observer observer : observerList) {
            observer.update();      // 변경사항 알림
        }
    }

    public void measurementsChanged() {
        notifyObserver();
    }

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

나머지 부분은 전부 동일하지만,

업데이트 하는 부분에서 파라미터가 제거된 점 과

getter 가 생긴 부분이 다르다.

 

이유는 Observer 들에서 찾아볼 수 있다.

// Observer 인터페이스
package observerpattern.observer;

public interface Observer {
    void update();
}

// 옵저버 구현체에서 변경된 부분

public class CurrentConditionDisplay implements Observer, DisplayElement {
   
    @Override
    public void update() {
        this.temperature = weatherData.getTemperature();
        this.humidity = weatherData.getHumidity();
    }
}

이 코드를 보면 전체적인 코드의 구조는 동일하다.

주제(Subject) 에 변화가 생기면 모든 Observer 의 update 메서드를 실행시킨다. 

이 때, 각각의 Observer 들은 본인이 필요한 정보를 주제(Subject) 에서 직접 가져와서 상태를 업데이트 한다. 

이런 방식을 사용하면 각 Observer 들은 필요한 정보만 가져와서 상태를 업데이트 할 수 있다.

 

 

 

 

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

상속(Inheritance)과 구성(Composition)  (0) 2023.02.10
디자인패턴과 전략패턴  (0) 2022.12.28

댓글