옵저버 패턴

강한친구·2022년 4월 2일
0

OOP Desing Pattern

목록 보기
4/15
post-thumbnail

신문 구독 메커니즘

  1. 신문사가 신문을 찍어낸다
  2. 구독자가 구독 신청을 하면 계속 신문을 공급한다. 구독자는 신문이 새로워 지거나 말거나 아무튼 계속 받는다.
  3. 구독해지를 하면 더 이상 받지 않는다.

비슷한 원리로 작동하는게 옵저버패턴이다.

Subject 객체에서 데이터를 관리하고, observer 객체에 데이터가 변하면 뿌려주는 방식을 취한다.

observer의 observer가 존재할수도 있다.

느슨한 결합

느슨한 결합이란 서로 상호작용은 하지만 상대방의 대해 잘 모르는 상태이다. 이러한 결합은 객체지향 디자인의 핵심이다.

API VS 인터페이스 구현

자바에 내장된 observable 슈퍼클래스를 사용한 구현방법과 인터페이스로 직접 구현하는 방법 두가지가 있다.

API 구현

Observable은 클래스이다. 클래스를 상속해서 쓰는 서브클래스를 만들어야한다. 만약 이미 다른 클래스를 사용하고 있다면 이 기능을 사용할 수 없다. 따라서 재사용성에 제약이 생긴다. 그리고 구현내용을 바꾸는것이 불가능하다는 단점이 있다.

또한, setChange()를 제외한 모든 함수에 protected가 걸려있어서, 외부에서 호출해서 사용할 수 없다. 이는 상속보다는 구성을 중시하는 디자인 원칙에 위배된다.

인터페이스 구현

Subject 인터페이스

package wData;

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObserver();
}

Observer 인터페이스

package wData;

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

DisplayElement 인터페이스

package wData;

public interface DisplayElement {
    public void display();
}

디스플레이 예시

package wData;

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

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

    @Override
    public void display() {
        System.out.println("Current Conditions = " + temperature + "F degrees and " + humidity + "% humidity");
    }

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

각 디스플레이들은 옵저버, 디스플레이 인터페이스를 동시에 받는다. Subject를 구현한 weatherData를 호출하고 자기자신을 옵저버로 등록한다. 그 후, update를 통해서 값을 최신화하고 display함수로 출력하는 구성을 가지고 있다.

옵저버 구현

package wData;

import java.util.ArrayList;

public class WeatherData implements Subject {
    private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList();
    }

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

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    @Override
    public void notifyObserver() {
        for (Object o : observers) {
            Observer observer = (Observer) o;
            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();
    }
}

서브젝트 인터페이스를 상속받고 구체적인 기능을 구현한다.
옵저버 디자인의 특성상 값이 변경사항이 생기면 모든 옵저버들에게 알려야한다.

이는 setMeasurement를 통해 구현된다.

setter를 통해 값이 set되면 값이 바뀐것을 알리는 measurementChanged 함수가 호출되고, 이 함수는 notify를 호출해서 모든 함수에게 이를 전달하고 update한다.

실행

package wData;

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

        CurrentConditionsDisplay cd = new CurrentConditionsDisplay(weatherData);
        //StatisticsDisplay staticsDisplay = new StatisticsDisplay(weatherData);
        //ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

주석처리된 코드는 다른 display 예시이다.

이렇게 구현하고 출력을 할 수 있다.

Current Conditions = 80.0F degrees and 65.0% humidity
Current Conditions = 82.0F degrees and 70.0% humidity
Current Conditions = 78.0F degrees and 90.0% humidity

정리

  1. 옵저버 패턴에서 변하는것은 주제의 상태와 옵저버의 개수, 형식이다. 이 패턴에서는 주제를 바꾸지 않더라도 주제에 의존하는 객체들을 바꿀 수 있다. 이는 변화에 유리하다

  2. 인터페이스로 프로그래밍을 하는것이 좋다. 클래스는 다중상속이 불가능하다. 부모가 여러명일수는 없기 때문이다.

만약 C라는 클래스가 클래스 A, B의 기능을 일부분씩 쓴다고하자. 이때, 이중상속이 불가능하기에 이 기능을 일부분씩 쓰려면 인터페이스를 사용해야한다.

하지만 반대의 경우도 존재한다. A, B 클래스가 C 인터페이스의 기능을 쓰려고하면, A, B는 공통으로 사용하는 기능들도 전부 오버라이딩해서 재정의를 해줘야한다.
그래도 못쓰는것보단 불편한게 나으니깐...

  1. 상속보다는 구성이 중요하다는 원칙

결론

객체지향 프로그래밍을 머리로만 이해하고 실제로 써본적이 거의 없어서 그런가 보고 따라할때는 이해가 가는데 막상 구현하려니깐 뇌가 멈춘다...

0개의 댓글