0

I read the Head First Design Patterns book and try to implement observer pattern in swift language. The code is below and the scenario is discussed after that.

protocol Observer:AnyObject{
    func update(temp: Float, humidity: Float, pressure: Float)
}

protocol Subject{
    func registerObserver(obs: Observer)
    func removeObserver(obs: Observer)
    func notifyObservers()
}

protocol DisplayElement{
    func display()
}

class WeatherData: Subject{
    private var observers = [Observer]()
    private var temp: Float = 0.0
    private var humidity: Float = 0.0
    private var pressure: Float = 0.0
    
    func registerObserver(obs: Observer) {
        observers.append(obs)
    }
    
    func removeObserver(obs: Observer) {
        let index = observers.firstIndex { $0 === obs }
        guard let unIndex = index else {
            return
        }
        observers.remove(at: unIndex)
    }
    
    func notifyObservers() {
        observers.forEach { obs in
            obs.update(temp: temp, humidity: humidity, pressure: pressure)
        }
    }
    
    func measurementsChanged() {
        notifyObservers()
    }
    
    func setMeasurements(temp: Float, humidity: Float, pressure: Float){
        self.temp = temp
        self.humidity = humidity
        self.pressure = pressure
        self.measurementsChanged()
    }

}

class CurrentConditionsDisplay{
    private var temp: Float = 0.0
    private var humidity: Float = 0.0
    private let weatherObj: WeatherData
    
    init(wea: WeatherData) {
        self.weatherObj = wea
        self.weatherObj.registerObserver(obs: self)
    }
}


extension CurrentConditionsDisplay: DisplayElement{
    func display() {
        print("Current Temperature = \(self.temp) and Humidity = \(self.humidity)")
    }
}

extension CurrentConditionsDisplay: Observer{
    func update(temp: Float, humidity: Float, pressure: Float) {
        self.temp = temp
        self.humidity = humidity
        self.display()
    }
}

Here, the scenario is you have a weather station which provide weather information in your WeatherData class. You job is, when WeatherData class changes a new state you just need to notify several devices who are observing or subscribing for your information. The weather station class is.

class weatherStation{
    func test() {
        let weatherData = WeatherData()
        _ = CurrentConditionsDisplay(wea: weatherData)
        _ = StatisticsDisplay(wea: weatherData)
        _ = ForecastDisplay(wea: weatherData)
        
        weatherData.setMeasurements(temp: 80, humidity: 60, pressure: 30)
        weatherData.setMeasurements(temp: 70, humidity: 60, pressure: 30)
        weatherData.setMeasurements(temp: 50, humidity: 60, pressure: 30)
    }
}

Here I am pushing fixed data into observer classes. Letter they introduce that we can pull the data which we need instead of pushing fixed data to every observer classes. Our(publisher) task is just to notify the observers that our state changes and pass ourself as a function argument. Then they(observer) use our getter methods whichever data they needed. Something like that.

func update(obs: Subject, arg: [String:Any])

It's ok to pass publisher object as argument but we can avoid this because every observer classes already have same related publisher object(private let weatherObj: WeatherData) which we pass. So why we pass it which is already have in our observer classes? Why not just use them? Is there any bad practice if you use them? Shouldn't I use this declaration inside observer classes as weak reference(private let weatherObj: WeatherData)?

NB:- I know about key value observer and push notification. The question is for clear the concept.

MD. Rejaul Hasan
  • 168
  • 1
  • 15

1 Answers1

0

So why we pass it which is already have in our observer classes? Why not just use them?

In update, you can refer to the observed subject just by saying weatherObj. This would only work in this specific case where the observer does have the subject as one of its properties, but that's not necessarily true in general.

One observer can observe multiple subjects, then in update, you'd most likely want to know which one of subjects that you are observing got updated.

The observer can also not have the subject as a property. For example, if you did this in CurrentConditionsDisplay instead:

weatherObj.registerObserver(obs: SomeOtherObserver())

Then in SomeOtherObserver.update, it can't use weatherObj to refer to the observed subject, because it's not CurrentConditionsDisplay anymore.

Passing the observed subject as the parameter of update makes it very convenient for whoever observing to get data out of the subject, not just CurrentConditionsDisplay.

Shouldn't I use this declaration inside observer classes as weak reference(private let weatherObj: WeatherData)?

Yes, there is indeed a strong reference cycle in the code, and making weatherObj weak would break the cycle. However, I think making the array of observers weak references would make more sense. KVO has a similar behaviour, namely that adding observers don't create a strong reference to them. It's not very trivial to make an array of weak references though.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • So, we should make array in subject class and weatherObj in observer class as weak reference? Thanks for your explanation to pass subject as a parameter. I understand the flexibility it provides. – MD. Rejaul Hasan Aug 08 '21 at 02:43
  • @Rumy IMO, the array should be weak, but do note that it's not as easy as adding the word `weak` to it. See the last link. And if you make the array weak, the `weatherObj` should not be weak, otherwise nothing retains anything! All the objects would be deinitialised! – Sweeper Aug 08 '21 at 02:46
  • yes, I saw it, need some time to understand. I understand why the array should be weak and why not weatherObj. – MD. Rejaul Hasan Aug 08 '21 at 02:53