Зачем нужен?
Паттерн Observer часто применяется для определения бизнес-логики(data model) в субъекте и делегирования view функций различным несвязанным наблюдателям. Т.е применяется во “View” части паттерна MVC.
Существует две реализации:
- pull(получение) изменений наблюдателем через геттер-методы
- pushing - активная доставка от субьекта
Соответствует Dependency Inversion принципу SOLID: субьект зависит не от наблюдателей, а от интерфейса Observer. Т.о наблюдателями могут быть любые объекты, реализующие данный интерфейс. В результате получаем слабую связанность(low coupling) между объектами.
Определение
- определяет отношение один-ко-многим таким образом, что при изменении состояния одного объекта происходит оповещение и обновление всех остальных зависимых объектов
- инкапсулирует основные(независимые) компоненты в абстракции Subject и изменяющиеся(опциональные) компоненты в Observers.
Реализация
- Разграничьте независимую(базовую, core) функциональность и зависимую(или опциональную) функциональность
- Смодулируйте независимую функциональность внутри subject
- Смодулируйте зависимую функциональность внутри observers(наблюдателей)
- Свяжите subject с интерфейсом observer
- Наблюдатели регистрирует себя самостоятельно
- Субъект пересылает сообщение об изменении состояния всем наблюдателям
- Субьект может выполнить активную доставку или наблюдатели могут сами получать необходимую информацию
Пример
В качестве примера: допустим у нас есть метеостанция, которая вызывает метод measurementsChanged у класса WeatherData при изменении температуры/влажности/давления.
Определяем независимую функциональность внутри subject:
class WeatherData: Subject {
private var temperature: Double!
private var humidity: Double!
private var pressure: Double!
}
Определяем зависимую функциональность внутри наблюдателей:
class CurrentConditionsDisplay: Observer, DisplayElement {
private var temperature: Double!
private var pressure: Double!
// зависимая функциональность в данном случае это
// представление температуры и давления в UI
func display() {
print("Temperature = \(temperature), pressure = \(pressure)")
}
}
Свяжем subject с observers:
class WeatherData: Subject {
private var observers = [Observer]()
}
Метеостанция посылает сообщения при изменении своего состояния:
class WeatherData: Subject {
func notifyObservers() {
for observer in observers {
// активная доставка
observer.update(temperature: temperature, humidity: humidity, pressure: pressure)
}
}
// вызов этой функции происходит извне при изменении данных(состояния)
func measurementsChanged() {
notifyObservers()
}
}
Наблюдатель получает изменения с помощью активной доставки:
class WeatherData: Subject {
func notifyObservers() {
for observer in observers {
// активная доставка
observer.update(temperature: temperature, humidity: humidity, pressure: pressure)
}
}
// вызов этой функции происходит извне при изменении данных(состояния)
func measurementsChanged() {
notifyObservers()
}
}
Зарегистрируем CurrentConditionsDisplay в качестве наблюдателя за метеостанцией:
class CurrentConditionsDisplay: Observer, DisplayElement {
// humidity нас не интересует, но доставка активная - принимаем все параметры
func update(temperature temperature: Double, humidity: Double, pressure: Double) {
self.temperature = temperature
self.pressure = pressure
display()
}
}
В данной реализации мы используем протокол DisplayElement, т.к предполагаем что у нас имеется несколько классов которые по разному представляют данные.