Observer Pattern

테사벨로그·2025년 10월 22일

Design Pattern

목록 보기
2/19

Observer Pattern 정리

1. 왜 Observer Pattern이 생겨났는가?

문제 상황

// ❌ 나쁜 예: 강하게 결합된 코드
public class WeatherData {
    public void measurementsChanged() {
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        
        // 구체적인 클래스에 직접 의존
        currentConditionDisplay.update(temp, humidity, pressure);
        statisticsDisplay.update(temp, humidity, pressure);
        forecastDisplay.update(temp, humidity, pressure);
    }
}

문제점:

  • 새로운 디스플레이를 추가하려면 코드 수정 필요
  • 실행 중에 디스플레이를 추가/제거 불가능
  • WeatherData가 모든 디스플레이를 알아야 함 (강한 결합)

2. Subject Interface VS Observer Interface

1. Subject Interface

  • "나를 관찰하고 싶으면 등록하세요"
  • Observer 관리의 규격만 정의
  • "has-a" 관계 (Subject는 Observer들을 가짐)
public interface Subject {
    void registerObserver(Observer o);    // 구독 신청
    void removeObserver(Observer o);       // 구독 취소
    void notifyObservers();                // 모든 구독자에게 알림
}

왜 Interface인가?

  • 날씨 데이터, 주식 데이터, 뉴스 피드 등 다양한 것이 Subject가 될 수 있음
  • Observer 관리 방식만 통일하면 됨

2. Observer Interface

  • "업데이트가 있으면 이 방식으로 알려주세요"
  • 알림 받는 규격만 정의
  • "can-do" 관계 (Observer는 알림을 받을 수 있음)
public interface Observer {
    void update(float temp, float humidity, float pressure);
}

왜 Interface인가?

  • 화면 표시, 로그 저장, 알림 전송 등 다양한 동작 가능
  • Subject는 Observer의 구체적인 구현을 몰라도 됨

3. 왜 Abstract Class가 아닌 Interface인가?

Interface를 사용하는 이유

  1. 다중 구현 가능

    // ✅ 가능: 하나의 객체가 Subject이면서 Observer가 될 수 있음
    public class WeatherAlert implements Subject, Observer {
        // 날씨를 관찰하면서 동시에 다른 객체들에게 알림
    }
  2. 느슨한 결합

    • Subject는 Observer 인터페이스만 알면 됨
    • 어떤 클래스든 Observer만 구현하면 구독 가능
    • 상속 계층과 무관하게 동작
  3. 공통 구현이 없음

    • 각 Observer의 update() 구현은 완전히 다름
    • 공유할 코드가 없으므로 Abstract Class 불필요

4. Observer Pattern 핵심 구조

      Subject (1)  ────────> Observer (N)
   (데이터 소유자)        (데이터 사용자들)
   
   - 일대다 관계
   - Subject가 변경되면 자동으로 모든 Observer에게 알림
   - Observer는 언제든 등록/해제 가능

5. 예시 코드

Step 1: 인터페이스 정의

// Subject 인터페이스
public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

// Observer 인터페이스
public interface Observer {
    void update(float temp, float humidity, float pressure);
}

Step 2: Subject 구현

public class WeatherData implements Subject {
    private ArrayList<Observer> observers;  // Observer 목록
    private float temperature;
    private float humidity;
    
    public WeatherData() {
        observers = new ArrayList<Observer>();
    }
    
    // Observer 등록
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    
    // Observer 제거
    public void removeObserver(Observer o) {
        observers.remove(o);
    }
    
    // 모든 Observer에게 알림 (핵심!)
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }
    
    // 데이터 변경 시 자동 알림
    public void setMeasurements(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();  // 자동으로 모든 Observer에게 알림
    }
}

Step 3: Observer 구현

// 현재 날씨 표시
public class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    
    // 생성자에서 Subject에 등록
    public CurrentConditionsDisplay(Subject weatherData) {
        weatherData.registerObserver(this);  // 나를 등록해줘!
    }
    
    // Subject로부터 알림 받음
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        display();  // 자동으로 화면 업데이트
    }
    
    public void display() {
        System.out.println("현재: " + temperature + "°F, " + humidity + "%");
    }
}

// 통계 표시
public class StatisticsDisplay implements Observer {
    private float maxTemp = 0.0f;
    private float minTemp = 200.0f;
    private float tempSum = 0.0f;
    private int numReadings = 0;
    
    public StatisticsDisplay(Subject 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("평균/최고/최저 = " 
            + (tempSum/numReadings) + "/" + maxTemp + "/" + minTemp);
    }
}

Step 4: 실행

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        
        // Observer들 생성 (생성과 동시에 자동 등록)
        CurrentConditionsDisplay currentDisplay = 
            new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = 
            new StatisticsDisplay(weatherData);
        
        // 데이터 변경 → 자동으로 모든 디스플레이 업데이트!
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

출력 결과

현재: 80.0°F, 65.0%
평균/최고/최저 = 80.0/80.0/80.0

현재: 82.0°F, 70.0%
평균/최고/최저 = 81.0/82.0/80.0

현재: 78.0°F, 90.0%
평균/최고/최저 = 80.0/82.0/78.0

6. Java 내장 Observable 사용

import java.util.Observable;
import java.util.Observer;

// Subject 구현 (Observable 상속)
public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    
    public void setMeasurements(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        setChanged();           // ⚠️ 중요: 변경 플래그 설정
        notifyObservers();      // 알림 발송
    }
    
    public float getTemperature() { return temperature; }
    public float getHumidity() { return humidity; }
}

// Observer 구현
public class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    
    public CurrentConditionsDisplay(Observable observable) {
        observable.addObserver(this);  // 등록
    }
    
    // Observable로부터 알림 받음
    public void update(Observable obs, Object arg) {
        if (obs instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
    
    public void display() {
        System.out.println("현재: " + temperature + "°F, " + humidity + "%");
    }
}

setChanged()를 사용하는 이유:

  • 알림을 최적화하기 위해
  • 예: 센서 값이 미세하게 변해도 매번 알림하지 않고, 중요한 변화일 때만 알림

7. 핵심 정리

Observer Pattern의 구성

요소역할왜 Interface?
SubjectObserver 관리 및 알림 발송다양한 데이터 소스가 Subject가 될 수 있음
Observer알림 수신 및 처리다양한 방식으로 알림을 처리할 수 있음
Concrete Subject실제 데이터 소유Subject를 구현
Concrete Observer실제 동작 수행Observer를 구현

언제 사용하는가?

  • 한 객체의 변화가 여러 객체에 영향을 줄 때
  • 느슨한 결합이 필요할 때
  • 실행 중 Observer 추가/제거가 필요할 때
  • 브로드캐스팅 방식 알림이 필요할 때

핵심 원칙

  • 일대다 의존성: 하나의 Subject, 여러 Observers
  • 느슨한 결합: Subject와 Observer는 인터페이스로만 상호작용
  • 자동 알림: Subject가 변경되면 자동으로 모든 Observer 업데이트
  • 동적 구독: 실행 중에 Observer 등록/해제 가능
profile
다들 응원합니다.

0개의 댓글