[Design Pattern] Observer Pattern

younghyun·2022년 10월 26일
0

Design Pattern

목록 보기
7/14
post-thumbnail

Observer Pattern 이란

Subject에 여러 Observer를 등록해 두고, Notify를 하게 되면 루프를 돌면서 각 Observer를 Update하는 패턴이다.

  • Subject와 Observer가 느슨한 결합을 갖는 것이 중요하다
  • Observer 등록 순서 등에 특정 로직이 의존하지 않도록 한다

역할이 수행하는 작업

  • Subject (인터페이스 or 추상 클래스)
    : 뉴스 발행자 또는 유투버로, Observer들을 알고 있는 객체이며 여러 Observer가 Subject에 붙을 수 있다.
    • add: Subject에 Observer를 구독자로 등록한다.
    • remove: Subject에 등록한 Observer의 구독을 해지한다.
    • notify: Subject에서 모든 Observer에 정보를 전달한다.
  • Observer (인터페이스 or 추상 클래스)
    : 구독자로, Subject에 생긴 변화에 관심을 갖는다.
  • ConcreteSubject (Subject 구현 클래스)
    • ConcreteObserver에게 알려줘야 하는 상태를 저장한다.
    • 자신의 상태가 달라지면 ConcreteObserver에게 알려준다.
  • ConcreteObserver (Observer 구현 클래스)
    • update: Observer에서 오버라이드해서 사용

예시

날씨 분석 시스템

  • WeatherStation: 기상청
  • WeatherData: 기상청에서 제공하는 데이터 Subject
  • Display: 기상 조건을 보여주는 장치 Observer
    • 기상 측정 요소
      • 온도 temp
      • 습도 humidity
      • 기압 pressure
    • 화면에 표시할 것
      • 현재 날씨 currentConditionsDisplay
      • 날씨 예보 forecastDisplay
      • 기상 통계 statisticsDisplay

Bad Case

WeatherData (기상청 데이터)

class WeatherData {
    getTemperature()
    getHumidity()
    getPressure()
    measurementsChanged() 
}

Display (화면에 표시할 것)

public void measurementsChanged(){    // 기상 관측값이 갱신되면 호출

   // 바뀐 기상 측정값
   float temp = getTemperature();
   float humidity = getHumidity();
   float pressure = getPressure();
   
   // 디스플레이 화면 업데이트
   currentConditionDisplay.update(temp, humidty, pressure);    
   forecastDisplay.update(temp, humidty, pressure);
   statisticsDisplay.update(temp, humidty, pressure);
  • 문제점: 다른 화면을 추가하고자 한다면 measurementsChanged() 메소드를 고치지 않고서는 새로운 화면을 추가할 수 없다
  • 한 객체의 변화 -> 그 객체에 의존하는 다른 객체들이 자동으로 내용이 갱신
    • WeatherData의 변화-> Display 내용이 갱신

⭐디자인원칙: 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

Good Case

1. Subject 인터페이스를 구현하여 WeatherData를 주제객체로 만든다.

interface Subject {
	registerObserver()   // 옵저버 등록
	removeObserver()    // 옵저버 삭제
	notifyObserver()    // 옵저버에게 업데이트 알림
}

class WeatherData implements Subject {
	registerObserver()
	removeObserver()
	notifyObserver()
    
        // 상태를 설정하고 알기위한 getter, setter
	getTemperature()
	getHumidity()
	getPressure()
	measurementsChanged()
}

2. 현재날씨, 날씨예보, 기상통계 화면에서 구현해야 할 인터페이스를 정의한다.

interface Observer {
	update()   // 새로 갱신된 주제 데이터를 전달하는 인터페이스
}

interface DisplayElement {
	display()  // 화면에 표현시키는 인터페이스
}

3. 위의 인터페이스를 구현한 현재날씨, 날씨예보, 기상통계 클래스를 만든다.

class CurrentConditionsDisplay implements Observer, DisplayElement{
	update()
	display() { // 현재 측정 값을 화면에 표시 }
}

class ForecastDisplay implements Observer, DisplayElement{
	update()
	display() { // 날씨 예보 표시 }
}

class StatisticsDisplay implements Observer, DisplayElement{
	update()
	display() { // 평균 기온, 평균 습도 등 표시 }
}

4. 실제 구현

Subject (Interface)

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

Observer (Interface)

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

ConcreteSubject (Subject를 구현하는 클래스)

public class WeatherData implements Subject {

    private ArrayList observers;  // Generics: ArrayList<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    
    public WeatherData() {
        observers = new ArrayList();   // Generics: 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 (int i = 0; i < observers.size(); i++) {
            Observer observer = (Observer) observers.get(i);
	    // Generics: Observer observer = observers.get(i);
	    
            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 float getTemperature() {return temperature;}
    public float getHumidity() {return humidity;}
    public float getPressure() {return pressure;}
}

ConcreteObserver

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private Subject weatherData;
    
    public CurrentConditionsDisplay(Subject weatherData) {     // Subject라는 인터페이스를 구현한 어떤 클래스가 들어와도 똑같이 동작
        this.weatherData = weatherData;
        weatherData.registerObserver(this);    // 나 자신을 weatherData에 
    }
    
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    
    public void display() {
        System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}
public class ForecastDisplay implements Observer, DisplayElement {
    private float currentPressure = 29.92f;  
    private float lastPressure;
    private WeatherData weatherData;

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

    public void update(float temp, float humidity, float pressure) {
        lastPressure = currentPressure;
	currentPressure = pressure;
	display();
    }

    public void display() {
        System.out.print("Forecast: ");
	if (currentPressure > lastPressure) {
	    System.out.println("Improving weather on the way!");
	} else if (currentPressure == lastPressure) {
	    System.out.println("More of the same");
	} else if (currentPressure < lastPressure) {
	    System.out.println("Watch out for cooler, rainy weather");
	}
    }
}
public class StatisticsDisplay implements Observer, DisplayElement {
    private float maxTemp = 0.0f;
    private float minTemp = 200;
    private float tempSum= 0.0f;
    private int numReadings;
    private WeatherData weatherData;

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

    public void update(float temp, float humidity, float pressure) {
        tempSum += temp;
	numReadings++;

	if (temp > maxTemp) { maxTemp = temp;}
	if (temp < minTemp) { minTemp = temp;}
	
	display();
    }

    public void display() {
        System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
    }
}

Main

public class WeatherStation {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
	CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
	StatisticsDisplay statisticsDisplay = 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);
	weatherData.removeObserver(forecastDisplay);
	weatherData.setMeasurements(62, 90, 28.1f);
    }
}

느슨한 결합(Loose Coupling)의 장점

"느슨하게 결합되어 있다" 라는 것은 결합되어있는 둘이 상호작용을 하긴 하지만 서로에 대해 잘 모른다는 뜻이다.

SubjectObserver는 느슨한 결합 관계이다.
= Subject가 Observer에 대해 아는 것은 특정 인터페이스를 구현한다는 것뿐, Observer를 구현하는 실제 클래스와 역할은 모른다.

  • Observer를 언제든 새로 추가, 제거 가능
  • 새로운 Observer가 생겨도 Subject가 바뀌지 않음
  • Subject와 Observer는 서로 독립적으로 재사용 가능
  • Subject나 Observer가 바뀌더라도 서로에게 영향을 미치지 않음
profile
🌱 주니어 백엔드 개발자입니당

0개의 댓글