Swift Design Patterns: Observer

Зачем нужен?

Паттерн Observer часто применяется для определения бизнес-логики(data model) в субъекте и делегирования view функций различным несвязанным наблюдателям. Т.е применяется во “View” части паттерна MVC.

Существует две реализации:

  • pull(получение) изменений наблюдателем через геттер-методы
  • pushing - активная доставка от субьекта

Соответствует Dependency Inversion принципу SOLID: субьект зависит не от наблюдателей, а от интерфейса Observer. Т.о наблюдателями могут быть любые объекты, реализующие данный интерфейс. В результате получаем слабую связанность(low coupling) между объектами.

Определение

  • определяет отношение один-ко-многим таким образом, что при изменении состояния одного объекта происходит оповещение и обновление всех остальных зависимых объектов
  • инкапсулирует основные(независимые) компоненты в абстракции Subject и изменяющиеся(опциональные) компоненты в Observers.

Реализация

  1. Разграничьте независимую(базовую, core) функциональность и зависимую(или опциональную) функциональность
  2. Смодулируйте независимую функциональность внутри subject
  3. Смодулируйте зависимую функциональность внутри observers(наблюдателей)
  4. Свяжите subject с интерфейсом observer
  5. Наблюдатели регистрирует себя самостоятельно
  6. Субъект пересылает сообщение об изменении состояния всем наблюдателям
  7. Субьект может выполнить активную доставку или наблюдатели могут сами получать необходимую информацию

Пример

В качестве примера: допустим у нас есть метеостанция, которая вызывает метод 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, т.к предполагаем что у нас имеется несколько классов которые по разному представляют данные.

Реализация паттерна Observer: