관리 메뉴

deVlog

[λ””μžμΈ νŒ¨ν„΄] ν–‰μœ„ νŒ¨ν„΄ - Observer Pattern (μ˜΅μ €λ²„ νŒ¨ν„΄) λ³Έλ¬Έ

πŸ› οΈ Software Architecture/Design Pattern

[λ””μžμΈ νŒ¨ν„΄] ν–‰μœ„ νŒ¨ν„΄ - Observer Pattern (μ˜΅μ €λ²„ νŒ¨ν„΄)

은루밍 2025. 4. 2. 22:12

λͺ©μ°¨

     

    πŸ“‡ κ°œμš”

    μ˜΅μ €λ²„ νŒ¨ν„΄(Observer Pattern)은 객체의 μƒνƒœ λ³€ν™”λ₯Ό κ΄€μ°°ν•˜λŠ” κ΄€μ°°μžλ“€(μ˜΅μ €λ²„λ“€)μ—κ²Œ μžλ™μœΌλ‘œ ν†΅μ§€ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. 즉, ν•œ 객체의 μƒνƒœκ°€ λ³€ν•˜λ©΄ κ·Έ 객체에 μ˜μ‘΄ν•˜λŠ” λ‹€λ₯Έ κ°μ²΄λ“€μ—κ²Œ μžλ™μœΌλ‘œ μ•Œλ¦Όμ΄ κ°€κ³  μžμ‹ λ„ μ—…λ°μ΄νŠΈλ˜λŠ” 1:N μ˜μ‘΄μ„±μ„ μ •μ˜ν•œλ‹€.

     

    νŠΉμ§•

    • λ°œν–‰(Publish)-ꡬ독(Subscribe) λͺ¨λΈλ‘œλ„ μ•Œλ €μ Έ μžˆλ‹€.
    • λŠμŠ¨ν•œ κ²°ν•©(Loose Coupling) ꡬ쑰λ₯Ό λ§Œλ“€μ–΄ 객체 κ°„ μ˜μ‘΄μ„±μ„ μ΅œμ†Œν™”ν•œλ‹€.
    • λΆ„μ‚° 이벀트 핸듀링 μ‹œμŠ€ν…œμ„ κ΅¬ν˜„ν•˜λŠ” 데 이상적이닀.

     

    πŸ‘©πŸ»‍πŸ’» μ½”λ“œλ‘œ μ•Œμ•„λ³΄μž

    날씨 정보λ₯Ό μˆ˜μ§‘ν•˜κ³  λ‹€μ–‘ν•œ λ””μŠ€ν”Œλ ˆμ΄μ— μ—…λ°μ΄νŠΈν•˜λŠ” 예제λ₯Ό 톡해 μ˜΅μ €λ²„ νŒ¨ν„΄μ„ μ‚΄νŽ΄λ³΄μž.

    // 1. μ˜΅μ €λ²„ μΈν„°νŽ˜μ΄μŠ€ (κ΅¬λ…μž)
    interface Observer {
        void update(float temperature, float humidity, float pressure);
    }
    
    // 2. 주제(Subject) μΈν„°νŽ˜μ΄μŠ€ (λ°œν–‰μž)
    interface Subject {
        void registerObserver(Observer o);
        void removeObserver(Observer o);
        void notifyObservers();
    }
    
    // 3. ꡬ체적인 주제(ConcreteSubject) 클래슀
    class WeatherData implements Subject {
        private List<Observer> 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 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();
        }
    }
    
    // 4. ꡬ체적인 μ˜΅μ €λ²„(ConcreteObserver) ν΄λž˜μŠ€λ“€
    class CurrentConditionsDisplay implements Observer {
        private float temperature;
        private float humidity;
        private Subject weatherData;
        
        public CurrentConditionsDisplay(Subject weatherData) {
            this.weatherData = weatherData;
            weatherData.registerObserver(this);
        }
        
        @Override
        public void update(float temperature, float humidity, float pressure) {
            this.temperature = temperature;
            this.humidity = humidity;
            display();
        }
        
        public void display() {
            System.out.println("ν˜„μž¬ 날씨 μƒνƒœ: μ˜¨λ„ " + temperature + "°C, μŠ΅λ„ " + humidity + "%");
        }
    }
    
    class StatisticsDisplay implements Observer {
        private float maxTemp = 0.0f;
        private float minTemp = 200;
        private float tempSum = 0.0f;
        private int numReadings = 0;
        private Subject weatherData;
        
        public StatisticsDisplay(Subject weatherData) {
            this.weatherData = weatherData;
            weatherData.registerObserver(this);
        }
        
        @Override
        public void update(float temperature, float humidity, float pressure) {
            tempSum += temperature;
            numReadings++;
            
            if (temperature > maxTemp) {
                maxTemp = temperature;
            }
            
            if (temperature < minTemp) {
                minTemp = temperature;
            }
            
            display();
        }
        
        public void display() {
            System.out.println("날씨 톡계: 평균/졜고/μ΅œμ € μ˜¨λ„ = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
        }
    }
    
    class ForecastDisplay implements Observer {
        private float currentPressure = 29.92f;
        private float lastPressure;
        private Subject weatherData;
        
        public ForecastDisplay(Subject weatherData) {
            this.weatherData = weatherData;
            weatherData.registerObserver(this);
        }
        
        @Override
        public void update(float temperature, float humidity, float pressure) {
            lastPressure = currentPressure;
            currentPressure = pressure;
            display();
        }
        
        public void display() {
            System.out.print("일기 예보: ");
            if (currentPressure > lastPressure) {
                System.out.println("날씨가 μ’‹μ•„μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€!");
            } else if (currentPressure == lastPressure) {
                System.out.println("μ§€κΈˆκ³Ό λΉ„μŠ·ν•  것 κ°™μŠ΅λ‹ˆλ‹€.");
            } else {
                System.out.println("더 μ‹œμ›ν•˜κ³  λΉ„κ°€ 올 것 κ°™μŠ΅λ‹ˆλ‹€.");
            }
        }
    }
    
    // 5. ν΄λΌμ΄μ–ΈνŠΈ
    public class ObserverPatternExample {
        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);
            
            // 기상 데이터 μ„€μ • (이것이 μ˜΅μ €λ²„λ“€μ—κ²Œ μžλ™μœΌλ‘œ 톡지됨)
            System.out.println("첫 번째 기상 데이터 μ—…λ°μ΄νŠΈ:");
            weatherData.setMeasurements(27, 65, 30.4f);
            
            System.out.println("\n두 번째 기상 데이터 μ—…λ°μ΄νŠΈ:");
            weatherData.setMeasurements(28, 70, 29.2f);
            
            System.out.println("\nμ„Έ 번째 기상 데이터 μ—…λ°μ΄νŠΈ:");
            weatherData.setMeasurements(26, 90, 29.3f);
            
            // μ˜΅μ €λ²„ 제거 μ˜ˆμ‹œ
            weatherData.removeObserver(forecastDisplay);
            System.out.println("\n일기 예보 제거 ν›„ μ—…λ°μ΄νŠΈ:");
            weatherData.setMeasurements(25, 80, 30.6f);
        }
    }
    
    • Observer μΈν„°νŽ˜μ΄μŠ€λŠ” λͺ¨λ“  μ˜΅μ €λ²„κ°€ κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” update() λ©”μ„œλ“œλ₯Ό μ •μ˜ν•œλ‹€.
    • Subject μΈν„°νŽ˜μ΄μŠ€λŠ” μ˜΅μ €λ²„μ˜ 등둝, 제거, 톡지 λ©”μ„œλ“œλ₯Ό μ •μ˜ν•œλ‹€.
    • WeatherData ν΄λž˜μŠ€λŠ” Subjectλ₯Ό κ΅¬ν˜„ν•˜κ³  기상 데이터가 변경될 λ•Œ λ“±λ‘λœ λͺ¨λ“  μ˜΅μ €λ²„μ—κ²Œ μ•Œλ¦°λ‹€.
    • CurrentConditionsDisplay, StatisticsDisplay, ForecastDisplay ν΄λž˜μŠ€λŠ” Observerλ₯Ό κ΅¬ν˜„ν•˜μ—¬ 날씨 데이터가 μ—…λ°μ΄νŠΈλ  λ•Œλ§ˆλ‹€ 각자의 λ°©μ‹μœΌλ‘œ 정보λ₯Ό ν‘œμ‹œν•œλ‹€.

     

    ☘️ μž₯/단점

    βœ… μž₯점

    1. λŠμŠ¨ν•œ κ²°ν•©(Loose Coupling)
      • 주제(Subject)λŠ” μ˜΅μ €λ²„(Observer)에 λŒ€ν•œ ꡬ체적인 클래슀λ₯Ό μ•Œ ν•„μš”κ°€ μ—†λ‹€.
      • μ˜΅μ €λ²„λ“€μ΄ μ–΄λ–€ ν΄λž˜μŠ€μΈμ§€, 무엇을 ν•˜λŠ”μ§€ λͺ°λΌλ„ λœλ‹€λŠ” 것이 핡심이닀.
      • 이둜 인해 μ‹œμŠ€ν…œμ˜ μœ μ—°μ„±κ³Ό μž¬μ‚¬μš©μ„±μ΄ ν–₯μƒλœλ‹€.
    2. 개방/폐쇄 원칙(OCP) μ€€μˆ˜
      • κΈ°μ‘΄ μ½”λ“œλ₯Ό μˆ˜μ •ν•˜μ§€ μ•Šκ³ λ„ μƒˆλ‘œμš΄ μ˜΅μ €λ²„ 클래슀λ₯Ό μΆ”κ°€ν•  수 μžˆλ‹€.
      • 예: μƒˆλ‘œμš΄ λͺ¨λ°”일 μ•± λ””μŠ€ν”Œλ ˆμ΄λ₯Ό μΆ”κ°€ν•˜λ”λΌλ„ WeatherData ν΄λž˜μŠ€λŠ” λ³€κ²½ν•  ν•„μš”κ°€ μ—†λ‹€.
    3. λŸ°νƒ€μž„μ— 객체 κ°„ 관계 λ³€κ²½ κ°€λŠ₯
      • μ˜΅μ €λ²„λŠ” μ–Έμ œλ“ μ§€ λ™μ μœΌλ‘œ μΆ”κ°€ν•˜κ±°λ‚˜ μ œκ±°ν•  수 μžˆλ‹€.
      • μ΄λŠ” μ‹€ν–‰ 쀑인 μ‹œμŠ€ν…œμ˜ λ™μž‘μ„ μœ μ—°ν•˜κ²Œ λ°”κΏ€ 수 있게 ν•΄μ€€λ‹€.
    4. 1:N μ˜μ‘΄μ„± μ²˜λ¦¬μ— 적합
      • ν•œ 객체의 변경이 λ‹€λ₯Έ μ—¬λŸ¬ 객체에 영ν–₯을 λ―ΈμΉ  λ•Œ μœ μš©ν•˜λ‹€.
      • MVC(Model-View-Controller) μ•„ν‚€ν…μ²˜μ—μ„œ Modelκ³Ό View의 관계λ₯Ό κ΅¬ν˜„ν•  λ•Œ 자주 μ‚¬μš©λœλ‹€.

    ❌ 단점

    1. μ˜ˆμΈ‘ν•˜κΈ° μ–΄λ €μš΄ μ—…λ°μ΄νŠΈ μˆœμ„œ
      • λ“±λ‘λœ μˆœμ„œμ— 따라 μ˜΅μ €λ²„κ°€ μ—…λ°μ΄νŠΈλ˜λ―€λ‘œ, νŠΉμ • μˆœμ„œμ— μ˜μ‘΄ν•˜λ©΄ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€.
      • μ˜΅μ €λ²„ 간에 μ˜μ‘΄μ„±μ΄ μžˆμ„ 경우 μ˜ˆμƒμΉ˜ λͺ»ν•œ λ™μž‘μ΄ λ°œμƒν•  수 μžˆλ‹€.
    2. λ©”λͺ¨λ¦¬ λˆ„μˆ˜ κ°€λŠ₯μ„±
      • μ˜΅μ €λ²„ 등둝 ν›„ λͺ…μ‹œμ μœΌλ‘œ μ œκ±°ν•˜μ§€ μ•ŠμœΌλ©΄ λ©”λͺ¨λ¦¬ λˆ„μˆ˜κ°€ λ°œμƒν•  수 μžˆλ‹€.
      • 특히 μžλ°”μ™€ 같은 μ–Έμ–΄μ—μ„œ μ˜΅μ €λ²„κ°€ 더 이상 ν•„μš”ν•˜μ§€ μ•Šμ„ λ•Œ μ°Έμ‘°λ₯Ό ν•΄μ œν•΄μ•Ό ν•œλ‹€.
    3. λΆˆν•„μš”ν•œ μ—…λ°μ΄νŠΈ
      • λͺ¨λ“  μ˜΅μ €λ²„μ—κ²Œ 항상 ν†΅μ§€ν•˜λ―€λ‘œ 일뢀 μ˜΅μ €λ²„μ—κ²ŒλŠ” ν•„μš”ν•˜μ§€ μ•Šμ€ μ—…λ°μ΄νŠΈκ°€ 전달될 수 μžˆλ‹€.
      • μ΄λŠ” μ„±λŠ₯ μ €ν•˜λ₯Ό μ΄ˆλž˜ν•  수 μžˆλ‹€.
    4. λ³΅μž‘ν•œ 디버깅
      • 비동기 λ°©μ‹μœΌλ‘œ μ˜΅μ €λ²„λ₯Ό μ—…λ°μ΄νŠΈν•˜λ©΄ 디버깅이 μ–΄λ €μ›Œμ§ˆ 수 μžˆλ‹€.
      • μ—¬λŸ¬ μ˜΅μ €λ²„κ°€ μ„œλ‘œ λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜λ©΄ λ™μ‹œμ„± λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€.

     

    πŸ“Œ 정리

    μ˜΅μ €λ²„ νŒ¨ν„΄μ€ 객체 κ°„μ˜ μΌλŒ€λ‹€ 의쑴 관계λ₯Ό μ •μ˜ν•˜μ—¬ ν•œ 객체의 μƒνƒœκ°€ λ³€κ²½λ˜λ©΄ μ˜μ‘΄ν•˜λŠ” λͺ¨λ“  κ°μ²΄μ—κ²Œ μžλ™μœΌλ‘œ ν†΅μ§€λ˜κ³  μ—…λ°μ΄νŠΈλ˜λŠ” νŒ¨ν„΄μ΄λ‹€.

    이 νŒ¨ν„΄μ€ λŠμŠ¨ν•œ 결합을 톡해 μœ μ—°μ„±μ„ 높이고, λŸ°νƒ€μž„μ— 객체 κ°„μ˜ 관계λ₯Ό λ™μ μœΌλ‘œ λ°”κΏ€ 수 있게 ν•΄μ€€λ‹€. ν•˜μ§€λ§Œ μ—…λ°μ΄νŠΈ μˆœμ„œ 예츑의 어렀움, λ©”λͺ¨λ¦¬ λˆ„μˆ˜ κ°€λŠ₯μ„±, λΆˆν•„μš”ν•œ μ—…λ°μ΄νŠΈ, λ””λ²„κΉ…μ˜ λ³΅μž‘μ„± λ“±μ˜ 단점이 μžˆλ‹€.

    βœ… μ–Έμ œ μ‚¬μš©ν•˜λ©΄ μ’‹μ„κΉŒ?

    • ν•œ 객체의 λ³€ν™”κ°€ λ‹€λ₯Έ μ—¬λŸ¬ 객체에 영ν–₯을 λ―ΈμΉ  λ•Œ
    • 객체 κ°„μ˜ λŠμŠ¨ν•œ 결합이 ν•„μš”ν•  λ•Œ
    • μ–΄λ–€ μ΄λ²€νŠΈκ°€ λ°œμƒν–ˆμ„ λ•Œ λ‹€μ–‘ν•œ 객체듀이 λ‹€λ₯Έ λ°©μ‹μœΌλ‘œ λ°˜μ‘ν•΄μ•Ό ν•  λ•Œ
    • μ‹€μ‹œκ°„ 데이터 전솑이 ν•„μš”ν•œ λΆ„μ‚° μ‹œμŠ€ν…œμ—μ„œ
    • MVC μ•„ν‚€ν…μ²˜μ—μ„œ λͺ¨λΈκ³Ό 뷰의 관계λ₯Ό κ΅¬ν˜„ν•  λ•Œ

    ❌ μ–Έμ œ ν”Όν•΄μ•Ό ν• κΉŒ?

    • μ˜΅μ €λ²„ 간에 λ³΅μž‘ν•œ μ˜μ‘΄μ„±μ΄ μžˆμ„ λ•Œ
    • μ—…λ°μ΄νŠΈ μˆœμ„œκ°€ μ€‘μš”ν•  λ•Œ
    • λΆˆν•„μš”ν•œ μ•Œλ¦Όμ΄ μ„±λŠ₯ 문제λ₯Ό μΌμœΌν‚¬ 수 μžˆμ„ λ•Œ
    • λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ΄ μ€‘μš”ν•œ μ œν•œλœ ν™˜κ²½μ—μ„œ

     

    πŸ’‘ μžλ°”μ˜ λ‚΄μž₯ μ˜΅μ €λ²„ νŒ¨ν„΄

    Javaμ—μ„œλŠ” java.util.Observable ν΄λž˜μŠ€μ™€ java.util.Observer μΈν„°νŽ˜μ΄μŠ€λ₯Ό 톡해 μ˜΅μ €λ²„ νŒ¨ν„΄μ„ μ§€μ›ν–ˆμœΌλ‚˜, Java 9λΆ€ν„° Deprecatedλ˜μ—ˆλ‹€. λŒ€μ‹  λ‹€μŒκ³Ό 같은 λŒ€μ•ˆμ„ μ‚¬μš©ν•  수 μžˆλ‹€:

    1. PropertyChangeListener
      • java.beans νŒ¨ν‚€μ§€μ˜ PropertyChangeSupport와 PropertyChangeListenerλ₯Ό μ‚¬μš©
      • 속성 κ°’μ˜ 변경을 κ°μ§€ν•˜κ³  μ•Œλ¦Όμ„ λ³΄λ‚΄λŠ” 데 유용
    2. Flow API (Java 9+)
      • java.util.concurrent.Flowμ—μ„œ λ¦¬μ•‘ν‹°λΈŒ ν”„λ‘œκ·Έλž˜λ°μ„ 지원
      • Publisher, Subscriber, Subscription μΈν„°νŽ˜μ΄μŠ€λ₯Ό 제곡
    3. Event Listener
      • Swing, JavaFX λ“± GUI ν”„λ ˆμž„μ›Œν¬μ—μ„œ μ‚¬μš©λ˜λŠ” 이벀트 λ¦¬μŠ€λ„ˆ
      • μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€ 이벀트 μ²˜λ¦¬μ— 적합

     

    πŸ“š μ‹€μ œ μ‚¬μš© 사둀

    • GUI μ‹œμŠ€ν…œ: λ²„νŠΌ 클릭, ν‚€λ³΄λ“œ μž…λ ₯κ³Ό 같은 μ‚¬μš©μž 이벀트 처리
    • 이벀트 관리 μ‹œμŠ€ν…œ: 이벀트 λ°œμƒ μ‹œ μ—¬λŸ¬ ν•Έλ“€λŸ¬μ—κ²Œ 톡지
    • λ‰΄μŠ€ λ°œν–‰-ꡬ독 μ„œλΉ„μŠ€: μƒˆ λ‰΄μŠ€ λ°œν–‰ μ‹œ κ΅¬λ…μžμ—κ²Œ μ•Œλ¦Ό
    • μ†Œμ…œ λ―Έλ””μ–΄ μ•Œλ¦Ό μ‹œμŠ€ν…œ: μƒˆ ν™œλ™ λ°œμƒ μ‹œ μ‚¬μš©μžμ—κ²Œ μ•Œλ¦Ό
    • μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§ μ‹œμŠ€ν…œ: μ‹œμŠ€ν…œ μƒνƒœ λ³€ν™”λ₯Ό μ—¬λŸ¬ λͺ¨λ‹ˆν„°λ§ 도ꡬ에 전달

     

    πŸ“‹ μ°Έκ³