본문 바로가기

SW 설계/Design Pattern

[Design Pattern] Behavioral - 옵저버 패턴 (Observer Pattern)

728x90

Observer Pattern 이란 기본적으로 변화에 대한 Notification 을 받고 싶을 때 사용할 수 있는 Pattern 으로, publish & subscribe (구독) 패턴이라고도 한다. 다르긴 하지만 Mediator Pattern 과 더불어 특정 trigger 에 대한 callback 을 진행해주는 패턴으로 볼 수 있다. 

 

옵저버 패턴은 변화를 감지해서 변화를 반영해야하는 곳에 전달한다

 

 

위 상황처럼 Humidity / Temperature / Pressure 에 대해 변화를 감지하고 이를 데이터화하여 UI 에 보여주는 모델이 있다고 해보자. Naive 하게 짠다면 Weather Data 를 보관하는 곳에서 바뀐 값들을 받아와 각 UI 에 setting 해주는 방식으로 짜여질 것이다. 

 

 

// 어떤 방식으로든 변화를 감지해서 해당 method 가 발동된다
update(){
    
    float temp = getCurTemperature();
    float humidity = getCurHumidity();
    float pressure = getCurPressure();
    
    curDisplay(temp, humidity, pressure);
    // ... 변화를 일일이 반영한다 
}

 

 

이와 같은 방식은 변화해야하는 값이 추가되거나, 변화를 반영해야하는 Object 들이 추가될 경우 해당 method 를 매번 수정해줘야 하는 방식으로, OCP 가 전혀 지켜지지 않는다. (가령, 미세먼지 농도가 추가되어야 한다면, 보수되어야 하는 부분이 많아진다)



 

옵저버 패턴의 기본적은 UML 틀

 

 

옵저버 패턴은 위와 같이 변화 관찰의 대상이 되는 Publisher 에 대하여 Object 들이 Observer Interface 를 통해 subscribe / unsubscribe 할 수 있다. 즉, 변화가 발생할 때마다 Observer 들에게 notify 를 통한 데이터가 전송된다. Subject 는 Observer Interface 에 대해서만 알고 있고(변화 감지시 notify 를 해줘야 하기 때문), Concrete Observer Class 들에 대해선 아무 의존성이 없기 때문에, Observer 들의 추가 / 변경이 훨씬 용이하기 된다. (위 그림에서 Subject 는 관찰 대상, Observer 는 구독을 하여 변화에 대한 알림을 받는 대상이다)

 

 

위 그림에서 Subject 는 Observer 에 대해 아는게 없고 (Observer 객체들이 있다는 것만 알고 있다), Observer 역시 데이터만 전달받도록 설계된다면 Subject 에 대해 아는게 없다. 따라서, 이 패턴은 Loosely Coupled 된 패턴인데, 두 객체가 서로에 대해 조금만 알고 있는 상황이기 때문이다. (실제 객체 데이터 레벨에서 참여 수준을 제어하므로, Object Scope 의 디자인 패턴이다)

 

 

옵저버 패턴은 Subject 에 의존하지 않기 때문에 code - reuse 측면에서 매우 우수하다. 위 모델만 봐서는 파악하기 어려우니, WeathterData 의 예시로 계속 살펴보자.

 

 

/*
 관찰의 대상이되는 Publisher 이다
 Publisher 는 자신한테 등록된 Observer 들을 직접 제어할 수 있다
 */
public interface Subject {

    void registerObserver(Observer observer);

    void removeObserver(Observer observer);

    void notifyAllObservers();
}

 

 

/*
 Observer 는 action 에 대한 update 를 진행해줘야 한다
 > 현재 예시를 기준으로 생각한다
 */
public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

 

 

역할체들은 준비되었으니, 이제 구현체들을 준비한다. 관찰의 대상이 되는 측은 WeatherData 이며, Observer 로 등록되는 측은 CurrentConditionDisplay UI 장치이다. 

 

 

public class WeatherDataV1 implements Subject{

    private ArrayList<Observer> observers; // 자료구조의 형태로 observer 들을 보관한다

    /*
     현재 실제 Weather Data 들이 반영된다
     */
    private float temp;
    private float humidity;
    private float pressure;

    public WeatherDataV1(float temp, float humidity, float pressure) {
        this.observers = new ArrayList<>();
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
    }

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

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

    // 바뀐 날씨 Data 들을 받아서 update 를 시작한다
    public void setChangedMeasurements(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyAllObservers(); // 변화를 자신한테 관심있는 Observer 들에게 notify 날린다
    }

    @Override
    public void notifyAllObservers() {
        for (Observer observer : this.observers) {
            observer.update(temp, humidity, pressure);
        }
    }
}

 

 

 

public class CurrentConditionDisplayV1 implements Observer {

    private float temp;
    private float humidity;
    private float pressure;

    /*
     바뀌는 과정은 subject 에서 진행하고,
     전달받은 데이터를 Observer 에서는 사용만하면 된다
     */
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        showDisplay(); // 바뀌면 Display 를 바꾼다
    }

    private void showDisplay() {
        System.out.println("Current Condition (T,H,P): (" + temp + ", " + humidity + ", " + pressure + ")");
    }
}

 

 

이와 같이 Subject 에 대해 notify 를 받고 싶은 UI 들은 쉽게 Subject.addObserver() 형태로 등록만 하면 된다. 위와 같은 방식으로 다음과 같이 Test 를 수행해볼 수 있다.

 

 

public static void main(String[] args) {

    // 해당 객체들은 자연스럽게 추가가 될텐데, Subject 와 Observer 의 역할체로써 선언되지는 않을 것 같다
    // 자신의 원래 역할대로 선언되는게 더 자연스러울 것 같은 느낌
    WeatherDataV1 weatherDataV1 = new WeatherDataV1(10, 10, 10);
    CurrentConditionDisplayV1 displayV1 = new CurrentConditionDisplayV1();

    weatherDataV1.registerObserver(displayV1);
    weatherDataV1.setChangedMeasurements(20, 20, 20);

    // 다른 Observer 도 추가하고 싶으면 간단하게 추가하면 된다

}


-- 실행 결과

Current Condition (T,H,P): (20.0, 20.0, 20.0)

 

 

Observer Pattern 은 각 Subject 와 Observer 의 Concrete 구현체들에 대한 의존도가 매우 낮기 때문에 code-reuse 가 우수하다. 이에 따라 Java 는 Observer Pattern 을 Observable, Observer Interface 로 제공한다 (Deprecated 되긴 했지만). Java Interface 를 사용하여 V2 를 만들어보자. (위에서 Subject 였던 객체는 Observable 객체로 상속된다)



/*
 V2 : Java 에서 제공하는 Subject (Observable) 을 사용한다
 > Observable abstract class 에 이미 Observer 보관소가 구현되어 있기 때문에,
 > add, remove, notify 등을 사용하기만 하면 된다
 > 다만, Observable 에게 자명하게 update 가 필요한 상황임을 전달하기 위해, Observable 에서 사용하는 boolean changed 값을 제어해줘야 한다
 */
public class WeatherDataV2 extends Observable {

    // Observer 보관소는 Observable 에 구현되어 있다
    private float temp;
    private float humidity;
    private float pressure;

    // 바뀐 날씨 Data 들을 받아서 update 를 시작한다
    public void setChangedMeasurements(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementChangeCallback(); // 변화를 notify 날린다
    }

    private void measurementChangeCallback() {
        super.setChanged(); // Observable 의 boolean attribute 를 변경하여 notify 상황임을 체크해둔다 (안해주면 notfiy 안함)
        super.notifyObservers();
    }

    public float getTemp() {
        return temp;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

 

 

public class CurrentConditionDisplayV2 implements Observer {

    // 등록은 외부에서 해줘도 되고, 내가 원하는대로 해주면 된다
    private Observable observable; 
    
    // display 수치들
    private float temp;
    private float humidity;
    private float pressure;

    public CurrentConditionDisplayV2(Observable observable) {
        this.observable = observable;
        this.observable.addObserver(this); // 등록은 원하는 방식대로 해줘도 된다
    }

    // Java 에서는 뭘 전달해야할지 모르기 때문에 알아서 하라고 객체 그 자체를 전달한다
    @Override
    public void update(Observable o, Object arg) { // update notify 가 발생하면 호출된다

        if (o instanceof WeatherDataV2) { // 내가 원하는 로직인지 체크
            WeatherDataV2 wd = (WeatherDataV2) o; // 바뀐 데이터를 가져올 수 있다
            this.temp = wd.getTemp();
            this.humidity = wd.getHumidity();
            this.pressure = wd.getPressure();
            showDisplay();
        } else if (o instanceof OTHER_OBSERVER){
            // ...
        } 
        
        // ...

        // 이와 같은 방식을 통해 1 개 이상의 Observable (Subject) 를 등록할 수 있다
        // 등록 방식만 잘 설계하면 된다
    }

    private void showDisplay() {
        System.out.println("Current Condition (T,H,P): (" + temp + ", " + humidity + ", " + pressure + ")");
    }

}

 

 

Subject Class 에서는 훨씬 간단하게 구현할 수 있고 Observer 측에 대한 그 어떤 의존성도 존재하지 않는다. Observer 들이 보관되어야 하는 자료구조도 관리해주기 위해 Observable 은 interface 가 아닌 abstract class 로 정의되었다. 그저 나는 Subject 객체이니 혹시나 나에게 등록된 애 있으면 알려라 정도로 구현된 수준으로, 별도 Observer 가 추가 / 삭제 / 변경되는 것도 모르게 할 수 있다. 

 

 

Observer 측 또한 여러 개의 Subject 를 구독할 수 있는 구조로 설계가 된 점을 알 수 있으며, 원하는 Observable 에게 잘 등록만 되도록 설계되면 된다 (위에서는 하나이므로 attribute 로 두어서 생성자에서 직접 등록하게 하였다). 또한 Subject 객체 그 자체를 전달받으므로 notify 받는 Observer 마다 Subject 객체를 각자 활용하여 다르게 사용할 수도 있다.

 

 

 

정리하며

 

 

Observer Pattern 은 특정 trigger 에 의해 다른 Object 들이 변경을 감지할 때 도입해볼만한 Pattern 이다. Subject 는 Observer 에 대해서 아는 것이 없어서 유지 / 보수에 매우 유리하며, Java API 까지 사용하면 Observer 등록과 notify 로 수신한 data handling 이 매우 용이하기 때문이다. 

 

 

Mediator 와 의존성 측면에서 많이 다르지만, callback 느낌의 pattern 이라는 점에서 유사한 것 같다. Java API 의 구조를 아니 훨씬 이해가 잘 된 것 같고, 앞으로 잘 사용해보면 좋을 것 같다. 

 

 

안쓰다가 Deprecate 된 이후로 써는게 국룰

 

 

 

출처

 

 

1) 에릭 프리먼, Head First Design Patterns』, O'Reilly Media (2004)

 

 

2) 중앙대학교 소프트웨어학부 이찬근 교수님 디자인 패턴 수업자료 중

728x90