옵저버 패턴
- 객체 사이에 일 대 가의 의존 관계를 정의해 두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 전달받고 자동으로 갱신될 수 있게 만든다.
활용성
- 어떤 추상 개념이 두 가지 양상을 갖고 하나가 다른 하나에 종속적일 때, 각 양상을 별도의 객체로 캡슐화하여 이들 각각 재사용할 수 있다.
- 한 객체에 가해진 변경으로 다른 객체를 변경해야 하고, 프로그래머들은 얼마나 많은 객체들이 변경되어야 하는지 몰라도 될 때
- 어떤 객체가 다른 객체에 자신의 변화를 통보할 수 있는데, 그 변화에 관심있어 하는 객체들이 누구인지에 대한 가정 없이도 통보가 될 때
구조
요소
- Subject : 감시자들을 알고 있느 주체, 감시자 객체를 붙이거나 떼는 데 필요한 인터페이스 제공
- Observer : 주체에 생긴 변화에 관심 있는 객체를 갱신하는 데 필요한 인터페이스 정의, 주체의 변경에 따라 변화되어야 하는 객체들의 일관성 유지
- ConcreteSubJect : 객체에게 알려줘야 하는 상태저장
- ConcreteObserver : 객체에 대한 참조자 관리, 주체의 상태와 일관성을 유지해야 하는 상태를 저장, 갱신 인터페이스 구현
협력 방법
- ConcreteSubJect는 Observer의 상태와 자신의 상태가 달라지는 변경이 발생할 때마다 감시자에게 통보한다.
- ConcreteSubject에서 변경이 통보된 후, ConcreteObserver는 필요한 정보를 주체에게 질의하여 얻어온다. ConcreteObserver는 이 정보를 이용해서 주체의 상태와 자신의 상태를 일치시킨다.
장점
- 결합이 느슨해진다. Subject와 Observer 사이에 추상적인 결합도만이 존재
- 주체가 아는것은 감시자들의 리스트일 뿐이다.
- 이 감시자들은 Observer의 인터페이스만 따른다
- 브로드캐스트 방식의 교류를 가능하게 한다.
- 주체는 얼마나 많은 객체들이 이 변화 정보를 원하는지 알 필요가 없고, 단지 자신의 감시자에게만 상태 변화를 알려주면 감시자가 이 통보를 처리할지 무시할지를 결정한다.
단점
- 예측하지 못한 정보를 갱신한다.
- 감시자는 다른 감시자의 존재를 모르기 때문에 주체를 변경하는 비용에 대해 모른다.
- 감시자가 많으면 연속적인 수정이 발생할 수 있다.
- 추적이 어려울 수도 있다.
- 감시자가 무엇이 변경되었는지 알 수 있게 해 주는 별도의 프로토콜이 추가되면 변경을 유추할 수 있다.
예시 코드
import Foundation
protocol Observer: AnyObject {
func update(temperature: Float, humidity: Float)
}
protocol Subject: AnyObject {
func registerObserver(_ observer: Observer)
func removeObserver(_ observer: Observer)
func notifyObservers()
}
class WeatherStation: Subject {
private var observers = [Observer]()
private var temperature: Float = 0.0
private var humidity: Float = 0.0
func registerObserver(_ observer: Observer) {
observers.append(observer)
}
func removeObserver(_ observer: Observer) {
if let index = observers.firstIndex(where: { $0 === observer }) {
observers.remove(at: index)
}
}
func notifyObservers() {
for observer in observers {
observer.update(temperature: temperature, humidity: humidity)
}
}
func setMeasurements(temperature: Float, humidity: Float) {
self.temperature = temperature
self.humidity = humidity
measurementsChanged()
}
private func measurementsChanged() {
notifyObservers()
}
}
class CurrentConditionsDisplay: Observer {
private var temperature: Float = 0.0
private var humidity: Float = 0.0
func update(temperature: Float, humidity: Float) {
self.temperature = temperature
self.humidity = humidity
display()
}
func display() {
print("Current conditions: \(temperature)F degrees and \(humidity)% humidity")
}
}
class ForecastDisplay: Observer {
private var currentPressure: Float = 29.92
private var lastPressure: Float = 0.0
func update(temperature: Float, humidity: Float) {
lastPressure = currentPressure
currentPressure = temperature
display()
}
func display() {
if currentPressure > lastPressure {
print("Forecast: Improving weather on the way!")
} else if currentPressure == lastPressure {
print("Forecast: More of the same")
} else {
print("Forecast: Watch out for cooler, rainy weather")
}
}
}
let weatherStation = WeatherStation()
let currentDisplay = CurrentConditionsDisplay()
let forecastDisplay = ForecastDisplay()
weatherStation.registerObserver(currentDisplay)
weatherStation.registerObserver(forecastDisplay)
weatherStation.setMeasurements(temperature: 80, humidity: 65)
weatherStation.setMeasurements(temperature: 82, humidity: 70)
weatherStation.setMeasurements(temperature: 78, humidity: 90)
}
참고