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.
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.
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.
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
- An observable keeps a list of observers.
notify()walks that list and callsupdate()on each one. - 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 letsupdate()actually read the changed data. - In C++, an observable holds observers as plain, non-owning pointers, not
unique_ptr, since it never owns their lifetime.
Keep reading