Hansel

디자인 패턴 / 옵저버 패턴 본문

CS/소프트웨어공학

디자인 패턴 / 옵저버 패턴

핑슬 2022. 5. 22. 18:01
옵저버 패턴?

 

패턴은 특정 객체가 관심 있어 하는 사건의 발생을 알려주어야 상황에 사용한다.

관찰하는 객체가 능동적으로 관찰하는 것이 아니라 관찰 대상으로부터 어떤 사건이 발생하였을 수동적으로 통보해주길 기다린다.

예시로 페이스북의 알림이나 유튜브 채널의 알림 등은 관찰자(구독자) 관찰 대상(채널)로부터 특정 상태의 변화를 수동적으로 전달받는다.

 

페이스북이나 유튜브의 예시처럼 객체와 여러 객체 1대다 관계를 맺을 있고 상태가 변하면 자동으로 관계를 맺은 다른 객체에 통보를 해준다.

 

Observer 패턴의 Interface 및 impl 관계

일반적으로 소프트웨어 설계에선 느슨한 결합이 중요하다.

결합도를 낮춤으로써 객체간의 독립성을 유지시키고 유지보수와 확장 등에 유연하게 대처할 수 있다.

 

Observer pattern 관찰자와 관찰 대상은 다음과 같은 이유로 느슨한 결합을 이룬다.

1. 관찰 대상이 관찰자에 대해 아는 것은 인터페이스 구현뿐이다.

2. 관찰자는 언제든지 새로 추가될 있다.

3. 새로운 형식의 관찰자라도 주제를 전혀 변경할 필요가 없다.

4. 관찰자와 관찰 대상은 서로 독립적으로 재사용 가능하다.

5. 관찰자나 관찰 대상의 변경이 일어나도 서로에게 영향을 미치지 않는다.

 

 

코드를 통해 확인

날씨와 관련된 app을 제작한다고 가정하자.

우리는 실시간 날씨 정보를 받아 서버에 저장된 날씨 데이터를 변경해야 하고 변경된 데이터를 

사용자들(Observer)에게 전달해야한다. 

 

Subject(관찰 대상)

public interface Subject {
    public void registerObserver(Observer o); //옵저버 등록
    public void removeObserver(Observer o); //옵저버 제거
    public void notifyObservers(); //변경 시 모든 옵저버에게 알려줌
}

 

Observer(관찰자)

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

 

Subject 구현체

public class WeatherData implements Subject {

    private ArrayList<Observer> observers = new ArrayList();
    private float temperature;
    private float humidity;
    private float pressure;

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

    @Override
    public void removeObserver(Observer o) {

    }

    @Override
    public void notifyObservers() {
        observers.stream().forEach(o-> o.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();
    }
}

setMeasurements 메서드를 통해 실시간으로 날씨 데이터를 제공받았다고 치자.

그럼 그 데이터를 받아 현재 우리의 날씨 데이터를 수정해준다.

날씨 데이터가 변경되었다면 당연히 그 사실을 Observer에게 전달해야한다.

 

measurementsChanged -> notifyObservers 메서드를 거치며 현재 등록된 Observer가 가진 정보들을 최신 정보들로 갱신해준다.

 

public class CurrentConditionDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private Subject weatherData;

    //weatherData = 관찰 대상
    //현재 currentConditionDisplay 클래스는 관찰자(Observer)

    //weatherData의 변경 사항을 받아서 적용
    public CurrentConditionDisplay(Subject 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();
    }
}

 

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

옵저버 인터페이스에서 작성된 update 메서드는 필요한 필드들만 가져와 정보를 갱신한다.

이 방식은 Push 방식으로 옵저버에 필요한 데이터들만 Subject가 push 하여 사용하는 전략이다

 

public interface Observer {
    public void update(WeatherData weatherData);
}

이런 방식이 존재할 수도 있다.

이 방식은 pull 방식이다.

 

이렇게 될 경우 구현체의 update 로직은 다음과 같이 변경된다.

 

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

얼핏 보면 알겠지만 아래의 pull 방식은 해당 구현체에 의존하게 된다.

따라서 결합도가 높아지고 필요한 데이터 외에 추가 데이터까지 받아오게 된다.

 

따라서 낮은 결합도를 위해 권장되는 방식은 push 방식이라 할 수 있다.

'CS > 소프트웨어공학' 카테고리의 다른 글

디자인 패턴 / 데코레이터 패턴  (0) 2022.05.22
디자인 패턴 / 전략패턴  (0) 2022.05.22