제2장 옵저버 패턴

샘플 요구 사항

기상 관측이 업데이트될 때마다 세 가지 표시 항목(현재 상태 표시, 기상 통계 표시, 일기예보 표시)을 업데이트해야 합니다.

// 구체적인 구현 -> 확장에 유연하지 못한 코드
public class WeatherData {
    public void measurementsChanged() {
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
    }

    // 바뀔 수 있는 부분 -> 캡슐화 필요, 디스플레이 항목과 데이터 주고받는 관계에서 공통 인터페이스
    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
     forecastDisplay.update(temp, humidity, pressure);
}

관찰자 패턴

한 개체의 상태가 변경되면 다른 종속 개체에 알림이 전송되고 자동으로 업데이트되도록 일대다 종속성을 정의합니다.

  • 주제(1) : 관찰자(들)
  • 주제에는 연관된 관찰자가 있으며 주제의 상태가 변경되면 정보가 관찰자에게 전달됩니다.
  • 일반적으로 주체 인터페이스와 관찰자 인터페이스를 포함하는 클래스 디자인으로 구현

즉, 주체는 상태를 저장하고 제어하며 관찰자는 상태를 사용하지만 반드시 소유하지는 않습니다.

또한 다수의 옵저버가 있을 수 있으며, 그들은 주체 의존적이며 주체가 자신의 상태가 변경되었음을 알리기를 기다립니다(예: 이벤트 리스너도 옵저버 패턴의 일부입니다).


느슨한 결합

객체가 상호 작용할 수 있지만 서로를 잘 알지 못하는 관계

  • 주체는 관찰자가 특정 인터페이스(관찰자 인터페이스)를 구현한다는 사실만 알고 있습니다.
  • 관찰자는 언제든지 추가할 수 있습니다.
    • 주제는 Observer 인터페이스를 구현하는 객체 목록에만 의존합니다.
  • 새 옵저버 유형을 추가해도 주제를 변경할 필요가 없습니다.
    • 마찬가지로 새 클래스에 관찰자 인터페이스를 구현하고 관찰자로 등록하기만 하면 됩니다.
  • 주제와 관찰자는 독립적으로 재사용할 수 있습니다.
  • 모티브나 보는 사람이 바뀌어도 서로 영향을 주지 않는다.

설계 원칙

  • 가능할 때마다 상호 작용하는 개체 간에 느슨한 결합을 사용해야 합니다.

느슨하게 결합된 디자인을 사용하면 변경 사항이 발생할 때 이를 처리할 수 있는 유연한 객체 지향 시스템을 만들 수 있습니다.

2 구현

구현 방식의 차이지만 일반적으로 옵저버가 필요한 데이터를 선택해서 가져오도록 하려면 PULL 방식을 사용하는 것이 좋습니다.

이렇게 하면 나중에 더 쉽게 확장할 수 있습니다.

1. PUSH 방식

주체와 관찰자

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

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

주제 구현 클래스

public class WeatherData implement Subject { 
    private List<Observer> observers; 
    private float temperature; 
    private float humidity; 
    private float pressure;

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

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

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

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

    // 갱신된 측정값을 받으면 실행되는 메서드
    public void measurementsChanged() {
        notifyObservers();
    }

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

관찰자 구현 클래스

public class CurrentConditionDisplay implement Observer { 
    private float temperature; 
    private float humidity; 
    private WeatherData weatherData;

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

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

    public void display() {
        System.out.println("~~");
    }
}

2. PULL 방식

주제에는 데이터에 대한 getter 메서드가 있으며 관찰자는 필요한 데이터를 얻을 때 해당 메서드를 호출합니다.

관찰자 인터페이스

인수 없이 호출하도록 메서드 변경

public interface Observer {
    public void update();
}

주제 구현 클래스

게터 메서드 추가

public class WeatherData implement Subject {

     private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure; 

    public void notifyObservers() {
        for (Observer observer: observers) {
            opserver.update();
        }
    }

    public float getTemperature() {
        return this.temperature;
    }

     public float getHumidity() {
        return this.humidity;
    } 

     public float getPressure() {
        return this.pressure;
    } 
}

관찰자 구현 클래스

주제에 구현된 getter 메서드를 호출합니다.

public class CurrentConditionDisplay implement Observer {

    // ...

    private WeatherData weatherData;

    public void update(float temp, float humidity, float pressure) {
         this.temperature = weatherData.getTemperature();
        this.humidity = weatherData.getHumidity();
        display();
    }
}

옵저버 패턴에 적용된 디자인 원칙

애플리케이션에서 달라지는 부분을 찾아 그렇지 않은 부분과 분리합니다.

  • 응용 프로그램의 다른 부분: 주제의 상태, 관찰자 ​​수
  • 응용 프로그램에서 변경되지 않는 부분: 주제 자체는 변경할 필요가 없습니다.

구현 대신 인터페이스하는 프로그램.

  • 주제와 관찰자는 인터페이스에서 분리됩니다.

상속 대신 구성을 사용하십시오.

  • 주제 내 관찰자 목록을 구성하고 관리합니다.