[Design Pattern] Observer Pattern

Loopy·2022년 2월 8일
0

디자인패턴

목록 보기
2/9
post-thumbnail

☁️ Observer Pattern 개념

1:N 의존 관계를 정의하는 pattern으로, 한개의 객체 상태가 변경될때, 의존관계에 있는 모든 객체들**이 자동으로 알림을 받고 상태를 갱신하는 패턴이다.

  • 일종의 푸시(Push) 서비스를 구현한다.

  • 결과: Loose Coupling, 확장성(누구든지 등록 가능)

☁️ Observer Pattern 절차와 설계

1. 정보 제공자(Subject)
  • 상태가 변경되면 알림 기능(notify)
  • 알림 대상이 되는 옵저버를 사전 등록(register)
  • Observable 또는 Publisher
  1. 정보 수신자(Observer)
  • 정보 제공자의 상태가 변경되면 그 내용을 받아서 반영함(update)
  • Subscriber

☁️ Observer Pattern 예시

기상 모니터링 시스템을 구축한다고 가정해보자.

  • 기상 스테이션 : 기상 정보를 수집하는 장비
  • WeatherData 객체 : 기상 스테이션으로부터 오는 데이터를 추적하는 객체 (Subject)
  • 디스플레이 : 사용자에게 현재 기상 조건을 보여주는 장치(Observer)
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. 메서드 내부에서 구체적인 객체를 사용하고 있으므로, 실행 중에 디스플레이 항목을 추가하거나 제거할 수 없다. 즉, 유연성이 떨어진다.(Tightly Coupled)
  2. 바뀌는 부분, 즉 화면에 보여주는 부분이 캡슐화되어 있지 않아 새로운 디스플레이 항목이 추가되면 코드에 변경이 일어난다.

새로운 디스플레이 조건이 추가되어 시스템이 확장되면, 어떻게 기존 코드를 안건드리고 유연하게 대처할 수 있을까?

해결 방법

Observer Pattern을 사용하면, 주제와 구독자 간 느슨한 결합을 생성해낼 수 있다.

  1. 옵저버를 구현하는 실제 클래스 및 역할에 대해 몰라도 된다.
  2. 새로운 옵저버는 실행 중에도 쉽게 추가하거나 제거 가능하다.(실행 중에도 가능)
  3. 변화하는 부분인 옵저버가 새로 생겨도 주제쪽 코드가 바뀌지 않을 수
    있다.
  4. 각각 인터페이스와 추상 클래스이므로, 주제와 옵저버는 독립적으로 재사용할 수 있다.

인터페이스 목록

public interface Subject {
   public void registerObserver(Observer o);
   public void removeObserver(Observer o);
   public void notifyObservers();  // 상태가 변경되면 옵저버들에게 연락
} 

푸쉬를 받고자 하는 사용자가 registerObserver 를 통해 등록되면, 특정 상황이 발생했을 때(Event) notifyObservers() 를 통해 등록된 사용자에게 모두 알린다.

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

구현체 목록

public class WeatherData implements Subject {
    private ArrayList<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) {
      int i = observers.indexOf(o);
      if (i >= 0) {
        observers.remove(i);
      }
    }
    
    public void notifyObservers() {  
      for (Observer observer : observers) {
          observer.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();
    }
}
public class CurrentConditionsDisplay implements Observer, DisplayElement {
  private float temperature;
  private float humidity;
  private Subject weatherData;  //WeatherData 가 아닌 Subject(중요!)
  
  public CurrentConditionsDisplay(Subject weatherData) {
    this.weatherData = weatherData;
    weatherData.registerObserver(this);    //Subject에 등록 요청
  }
  
  public void update(float temperature, float humidity, float pressure) {  //Subject에서 호출됌
    this.temperature = temperature;
    this.humidity = humidity;
    display();
  }
  
  public void display() {
    System.out.println("Current conditions: " 
    + temperature + "F degrees and " + humidity 
    + "% humidity");
  }
}

PUSH 하지 말고 PULL 해오자!

현재 주제(Subject)에서 데이터들을 구독자들에게 일방적으로 전송하고 있다. 하지만, 만약 디스플레이에 보여줄 요소가 증가하면 update 메서드에 변경이 생기고 이는 모든 디스플레이 있는 update 메서드의 변경을 야기한다.

따라서, 구독자쪽에서 필요한 데이터를 골라서 가져가도록 하는 방법이 더 좋다. 단순히 게터 메서드만 추가해주면 된다.

public interface Observer {
    public void update();
}
public void update() {
	this.temperature = weatherData.getTemperature();
    this.humidity = weatherData.getHumidty();
    display();
}

PUB-SUB 패턴과 다른 개념이다. 구독자가 서로 다른 유형의 메시지에 관심을 가질 수 있고, 출판사와 구독자를 더 세세하게 분리할 수 있는 패턴

☁️ Oberver Pattern과 ApplicationEvent

서비스간 의존성을 낮추기 위해..
https://sevrain-chea.medium.com/how-to-implement-observer-pattern-using-springboot-events-106d80b9ea05
https://m.blog.naver.com/gngh0101/221337447578

profile
개인용으로 공부하는 공간입니다. 피드백 환영합니다 🙂

0개의 댓글