The Observer Pattern and Two Ways to Hand Over the Data

The design choice hiding inside update(): whether the observer has to reach back and pull the new data, or whether the notification just hands it over directly.

September 9, 20263 min read6 / 19

The last post built a working WeatherStation and PhoneDisplay. update() took no arguments, so PhoneDisplay had to save a reference to WeatherStation in its constructor, just so it had a way to go read the new temperature afterward.

That specific choice, saving a reference and reading it later, is not the only way to build this. Here's the other option, in one sentence: instead of an observer reaching back to pull the new data, hand the data over directly, right inside the notification itself.

What We Already Built Has a Name

What the last post built is called push-pull. The observable pushes the fact that something changed, that's the call to update(). But the observer still has to pull the actual value itself, by reaching back through the reference it saved and calling a getter like getTemperature().

Two separate steps: get told, then go fetch. Told first, fetched second.

The Other Option: Push-Push

There's a second way to build this. Instead of update() taking no arguments, give it the new value directly:

class IObserver { public: virtual ~IObserver() = default; virtual void update(int temperature) = 0; };

Now WeatherStation hands the new value straight to every observer:

void notify() { for (IObserver* observer : observers_) { observer->update(temperature_); } }

Notice what disappeared. PhoneDisplay no longer needs to save a reference to WeatherStation at all. It never calls getTemperature(). The value just arrives, already attached to the notification.

Which One to Reach For

Push-pull is the better default when an observer might need several different pieces of data, not just one, or might need to call more than one method on the observable. Saving a reference once in the constructor covers all of that, no matter how many getters get added later.

Push-push is leaner when there's exactly one obvious piece of data changing. It skips the saved reference and the extra method call entirely; the observer just reads the value it was handed.

Neither one is "the real observer pattern" and the other one a shortcut. They're the same pattern, just with the data handed over at a different step.

Run this yourself: All code examples in this series so far are in the code-practice repo. Clone it, then run g++ -std=c++17 main.cpp -o main && ./main (C++) or npm start (TypeScript) to see it run.

Try It Yourself

Here's the same shape, in a scenario you might actually build: a stock price tracker.

A StockPriceTracker holds the current price of one stock, and that price changes constantly. Several completely different things need to react the instant it does: an email alert service, an SMS alert service, and a trading bot that automatically buys when the price drops below a threshold.

  1. Design an IObserver interface with update(), and an IObservable interface with add(), remove(), and notify(). Pick push-pull or push-push for yourself.
  2. Build StockPriceTracker as the observable. Give it getPrice() and a setPrice(price) that updates the price and calls notify().
  3. Build EmailAlert and SmsAlert as observers. Each one should print a different message when it's told the price changed.
  4. Add a TradingBot observer that only prints "Buying!" when the price drops below a number you pick. Everything else should stay completely untouched to support it.

Same two shapes as WeatherStation: something that changes and keeps a list, and a handful of things that each react to that change differently.

The Essentials

  1. Push-pull tells the observer something changed, then makes it reach back through a saved reference to fetch the actual value.
  2. Push-push skips that reference entirely. The new value rides along with the notification itself, through update(value).
  3. Neither is more "correct." Push-pull suits several possible data points; push-push suits one obvious value.