티스토리 뷰

728x90

 

Observer Pattern

  • 어떤 객체의 상태가 바뀌면 그 객체를 구독하는 다른 객체(옵저버)에게 알림을 보내는 패턴

 

Subject 

  • Observer에게 데이터가 변경될 때마다 알려주는 역할
  • 새로운 값을 Observer에게 전달한다.
  • 옵저버 패턴의 대상이 되는 데이터를 관리한다.

Observer

  • Subject를 구독하고, Subject의 데이터가 바뀌면 그 데이터를 전달받는다.

 

 

특징

일대다 의존성

  • 하나의 Subject에 여러 Observer가 연관된다.
  • Observer는 데이터를 가질 필요가 없고, Subject가 알림을 보내길 기다린다. → Subject에 의존

 

여러 객체가 동일한 데이터를 관리하지 않고, 하나의 객체(Subject)만 가지고 있기 때문에 깔끔한 코드.

 

느슨한 결합 (Loose Coupling)

객체들끼리 상호작용을 하고 의존하지만, 서로의 세세한 부분까지 알 필요는 없는 것.

  1. Observer를 언제든 새로 추가/삭제할 수 있다.
  2. Subject는 Observer의 세부 기능을 모르고, Observer는 Subject의 세부 기능을 알지 못한다.
    • Subject는 Observer들이 Observer 인터페이스를 구현하고 있다는 사실만 알고 있다.
  3. 새로운 Observer 클래스를 추가해도 Subject의 코드를 변경할 필요가 없다.
  4. Subject/Observer 인터페이스를 구현하고 있다면, Subject/Observer의 내용이 달라져도 서로에게 영향을 미치지 않는다.
  5. Subject와 Observer는 서로 독립적으로 재사용할 수 있다.

 

 

옵저버 패턴 작동 원리 예시

  1. Subjec를 A, B,C 객체가 구독하고 있음 (Observer: A, B, C)
  2. D 객체가 새로 Subject를 구독함 (Observer: A, B, C, D)
  3. Subject의 데이터가 변경됨
  4. A, B, C, D는 데이터가 변경되었다는 알림을 받음
  5. C 객체가 Subject의 구독을 해지함 (Observer: A, B, D)
  6. Subject의 데이터가 변경됨
  7. A, B, D는 데이터가 변경되었다는 알림을 받음

 

 

구조

  • Subject
    • Subject를 나타내는 인터페이스
    • 메소드
      • 객체를 Observer로 등록 (Subject를 구독)
      • 객체를 Observer 목록에서 제외시킴 (Subject 구독을 해지) 
      • Observer에게 알림을 보냄
  • ConcreteSubject
    • Subject 인터페이스를 구현
  • Observer
    • Observer를 나타내는 인터페이스
    • 메소드
      • Subject에게 알림을 받았을 때 특정 로직을 처리
  • ConcreteObserver
    • Observer 인터페이스를 구현

 

 

예제 코드

요구 사항

  • 온도, 습도, 기압 데이터를 실시간으로 받아온다.
  • 보여줄 화면: 현재 조건 / 기상 통계 / 기상 예보
  • 다른 화면이 더 추가될 수 있도록 확장 가능해야 한다.

 

구현 방향

  • 현재 조건 / 기상 통계 / 기상 예보 화면들이 Observer가 되어 Subject를 구독한다.
  • Subject는 온도, 습도, 기압 데이터가 변경될 때마다 Observer에게 알림을 보낸다.
  • Subject에게 알림을 받은 Observer는 화면을 갱신한다.
  • Subject, Observer를 인터페이스로 구현하여 확장성을 높인다.

 

클래스 다이어그램

 

구현

  1. Subject, Obsever 인터페이스 생성
interface Subject {
    fun registerObserver(o: Observer)
    fun removeObserver(o: Observer)
    fun notifyObservers()
}
  • Subject에 들어가야 할 기능
    • Observer 등록
    • Observer 삭제
    • Observer에게 알림

 

interface Observer {
    fun update(temp: Float, humidity: Float, pressure: Float)
}
  • Observer에 들어가야 할 기능
    • Subject에게 알림을 받음

 

       2. Subject 인터페이스 구현

class WeatherData(private val observers: MutableList<Observer> = mutableListOf()) : Subject {
    private var temperature: Float = 0f
    private var humidity: Float = 0f
    private var pressure: Float = 0f

    override fun registerObserver(o: Observer) {
        observers.add(o)
    }

    override fun removeObserver(o: Observer) {
        observers.remove(o)
    }

    override fun notifyObservers() {
        for (o in observers) {
            o.update(temperature, humidity, pressure)
        }
    }

    fun setMeasurements(temperature: Float, humidity: Float, pressure: Float) {
        this.temperature = temperature
        this.humidity = humidity
        this.pressure = pressure
        notifyObservers()
    }
}
  • Observer를 저장하는 observers List 선언
  • Observer를 등록: List에 추가
  • Observer를 삭제: List에서 삭제
  • 데이터 변경: 인자로 들어온 데이터로 갱신 후 Observer에게 알림을 보냄
  • Observer에게 알림: List에 있는 Observer의 update() 호출

 

       3. Observer 인터페이스 구현 

class CurrentConditionsDisplay : Observer, DisplayElement {
    private var temperature: Float = 0f
    private var humidity: Float = 0f
    private val weatherData: WeatherData

    constructor(weatherData: WeatherData) {
        this.weatherData = weatherData
        this.weatherData.registerObserver(this)
    }

    override fun update(temperature: Float, humidity: Float, pressure: Float) {
        this.temperature = temperature
        this.humidity = humidity
        display()
    }

    override fun display() {
        println("현재 상태: 온도 {$temperature}F 습도 ${humidity}%")
    }
}
  • 보여줘야 하는 화면은 현재 조건 / 기상 통계 / 기상 예보 세개기 때문에, 위와 같은 구상 클래스를 3개 생성
  • 생성자에서 Subject에 Observer로 등록
  • Subject에게 알림을 받을 때마다 화면을 갱신하기 위한 update()를 구현

 

풀 방식

// 기존 push 방식
	
    // ConcreteSubject
    override fun notifyObservers() {
       for (o in observers) {
           o.update(temperature, humidity, pressure)
       }
   }
    
    // ConcreteObserver
    override fun update(temperature: Float, humidity: Float, pressure: Float) {
       this.temperature = temperature
       this.humidity = humidity
       display()
   }
  • 현재는 update를 통해 데이터 3개 모두를 전송한다.
    • 하지만 어떤 화면에서는 데이터 3개 중 일부만 필요할 수 있다.
    • 만약 다른 데이터를 추가해야 할 경우, 모든 화면의 update()를 변경해야 한다.
  •  Subject가 Observer에게 데이터를 보내는(push) 방식 말고, Observer가 Subject의 데이터를 가져오는(pull) 방식으로 변경할 수 있다.

 

// pull 방식
    
    // ConcreteSubject
    override fun notifyObservers() {
       for (o in observers) {
           o.update()
       }
   }
    
    // ConcreteObserver
    override fun update() {
       temperature = weatherData.temperature
       humidity = weatherData.humidity
       display()
   }
  • Subject가 Observer의 update()를 호출하면, update 함수 내에서 Subject의 게터를 직접 호출해서 데이터를 가져온다.
  • update()의 파라미터에는 아무것도 없기 때문에 Subject에서 Observer에게 데이터를 전달하지 않고, 데이터가 변경되었다는 사실만 알려준다.

 

 

 

728x90

'app > design pattern' 카테고리의 다른 글

[디자인 패턴] Factory Pattern  (0) 2023.07.05
[디자인 패턴] Singleton Pattern  (0) 2023.07.05
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함