The Observer Pattern and Letting the Weather Station Do the Telling

Turning the weather station idea into real code: a list of watchers, one notify() call that reaches all of them, and the small trick that lets each one read the new temperature.

September 9, 20267 min read5 / 19

The last post ended with two jobs. An observable needs to add watchers, remove watchers, and notify all of them. An observer just needs a way to be told something changed.

Here's the fix, in one sentence: give the weather station a list of displays, and the moment its temperature changes, walk that list and call update() on every single one.

The Two Shapes This Pattern Needs

Both jobs start as a shape, an interface, before either one gets built for real.

class IObserver { public: virtual ~IObserver() = default; virtual void update() = 0; }; class IObservable { public: virtual ~IObservable() = default; virtual void add(IObserver* observer) = 0; virtual void remove(IObserver* observer) = 0; virtual void notify() = 0; };

Notice the Java version calls its broadcast method notifyObservers(), not notify(). That's not a style choice. Every single object in Java already has a built-in notify() method, used for a completely unrelated thing (waking up a waiting thread). Naming a method notify() here would technically work, but it would quietly collide with that built-in one. Giving it a different name sidesteps the whole problem.

The Weather Station Keeps a List

A WeatherStation is a concrete IObservable. Its whole job is to remember who's watching, and tell all of them the moment its temperature changes.

class WeatherStation : public IObservable { public: void add(IObserver* observer) override { observers_.push_back(observer); } void remove(IObserver* observer) override { observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end()); } void notify() override { for (IObserver* observer : observers_) { observer->update(); } } int getTemperature() const { return temperature_; } void setTemperature(int temperature) { temperature_ = temperature; notify(); } private: std::vector<IObserver*> observers_; int temperature_ = 0; };

Read notify() slowly, because it's the whole pattern in four lines. Walk the list. For each observer in that list, call update(). That's it. Nothing fancy is happening here, just a loop.

One more thing worth noticing: in the C++ version, observers_ holds plain pointers, not unique_ptr. That's on purpose. The last pattern had Duck own its boxes, because nothing else needed them. Here, WeatherStation does not own its observers. A PhoneDisplay exists whether or not any weather station is watching it, and it should keep existing even if you remove it from the list. So a plain, non-owning pointer is the right tool, not a mistake.

A WeatherStation calling update() on PhoneDisplay, WindowDisplay, and any other observer in its list ExpandA WeatherStation calling update() on PhoneDisplay, WindowDisplay, and any other observer in its list

A Display That Remembers Which Station It Watches

update() alone only tells a display that something changed. It doesn't say what. So a PhoneDisplay needs one more thing: a way to actually go read the new temperature once it's been told.

class PhoneDisplay : public IObserver { public: explicit PhoneDisplay(WeatherStation* station) : station_(station) {} void update() override { std::cout << "Phone: it's now " << station_->getTemperature() << " degrees.\n"; } private: WeatherStation* station_; };

Here's the piece that trips almost everyone up the first time. PhoneDisplay takes a WeatherStation in its constructor, and saves it. That's not the same thing as registering as an observer. Registering happens through add(). This is something else entirely.

Without that saved reference, update() would have nothing to work with. It would know a change happened, but it would have no way to actually read the new temperature. The reference is what turns "something changed" into "here's what it changed to."

Wiring It All Together

WeatherStation station; PhoneDisplay phone(&station); station.add(&phone); station.setTemperature(72); // Phone: it's now 72 degrees.

Walk through what actually happens here, one line at a time.

`station` is created
An observable with nobody watching yet.
`phone` is created
It's handed `station` right away, so it always knows which station to read from.
`station.add(phone)` runs
Registers `phone` as a watcher. Now `station` knows `phone` wants to hear about changes.
`setTemperature(72)` runs
Changes the number, then calls `notify()` on its own, which walks the list and calls `update()` on `phone`.
Inside `update()`
`phone` reads `station.getTemperature()` through the reference it saved back in step 2, and prints the new value.

Add a WindowDisplay the exact same way, register it with station.add(), and the very next setTemperature() call reaches both displays automatically. WeatherStation never needs to change to support a new kind of display.

The next post looks at a real design choice hiding inside this update() method, and closes out with a hands-on exercise.

The Essentials

  1. An observable keeps a list of observers. notify() walks that list and calls update() on each one.
  2. Saving a reference to the observable in the observer's constructor is not the same as registering, that happens through add(), and it's what lets update() actually read the changed data.
  3. In C++, an observable holds observers as plain, non-owning pointers, not unique_ptr, since it never owns their lifetime.