티스토리 뷰
728x90
Observer Pattern
- 어떤 객체의 상태가 바뀌면 그 객체를 구독하는 다른 객체(옵저버)에게 알림을 보내는 패턴
Subject
- Observer에게 데이터가 변경될 때마다 알려주는 역할
- 새로운 값을 Observer에게 전달한다.
- 옵저버 패턴의 대상이 되는 데이터를 관리한다.
Observer
- Subject를 구독하고, Subject의 데이터가 바뀌면 그 데이터를 전달받는다.
특징
일대다 의존성
- 하나의 Subject에 여러 Observer가 연관된다.
- Observer는 데이터를 가질 필요가 없고, Subject가 알림을 보내길 기다린다. → Subject에 의존
여러 객체가 동일한 데이터를 관리하지 않고, 하나의 객체(Subject)만 가지고 있기 때문에 깔끔한 코드.
느슨한 결합 (Loose Coupling)
객체들끼리 상호작용을 하고 의존하지만, 서로의 세세한 부분까지 알 필요는 없는 것.
- Observer를 언제든 새로 추가/삭제할 수 있다.
- Subject는 Observer의 세부 기능을 모르고, Observer는 Subject의 세부 기능을 알지 못한다.
- Subject는 Observer들이 Observer 인터페이스를 구현하고 있다는 사실만 알고 있다.
- 새로운 Observer 클래스를 추가해도 Subject의 코드를 변경할 필요가 없다.
- Subject/Observer 인터페이스를 구현하고 있다면, Subject/Observer의 내용이 달라져도 서로에게 영향을 미치지 않는다.
- Subject와 Observer는 서로 독립적으로 재사용할 수 있다.
옵저버 패턴 작동 원리 예시
- Subjec를 A, B,C 객체가 구독하고 있음 (Observer: A, B, C)
- D 객체가 새로 Subject를 구독함 (Observer: A, B, C, D)
- Subject의 데이터가 변경됨
- A, B, C, D는 데이터가 변경되었다는 알림을 받음
- C 객체가 Subject의 구독을 해지함 (Observer: A, B, D)
- Subject의 데이터가 변경됨
- 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를 인터페이스로 구현하여 확장성을 높인다.
클래스 다이어그램
구현
- 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
'cs > design pattern' 카테고리의 다른 글
[디자인 패턴] Factory Pattern (0) | 2023.07.05 |
---|---|
[디자인 패턴] Singleton Pattern (0) | 2023.07.05 |