-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
210: Practice Exercise: Implementing a Simple Observer-Based Event System
I've noticed a recurring pattern when developers first try to build event systems: they treat the "Subject" (the thing being watched) as a manager that knows exactly who is listening and what those listeners actually do. They think of it as a coordinator rather than a broadcaster.
The Myth: The Subject should call specific methods on specific classes
If you're building a game, you might think, "Okay, when the Player takes damage, the Player class should call UI::updateHealthBar(), SoundEngine::playOuchSound(), and AchievementSystem::checkForDeath()." On the surface, this works. It's direct and easy to trace in a debugger. But here is where it falls apart: the moment you want to add a CameraShake effect or a LogSystem, you have to go back into your Player class and add yet another function call and another header include.
Your Player class—which should only care about gameplay logic—is now suddenly burdened with knowing about the UI, the audio engine, and the achievement system. You've created a "God Object" that is tightly coupled to every other system in your project. If you delete the SoundEngine class, your Player class won't even compile.
The Reality: Broadcasters don't care who is listening
The core of the Observer pattern is decoupling. The Subject shouldn't know who is observing it, only that the observer adheres to a specific "contract" (an interface). Instead of the Subject calling updateHealthBar(), it should simply tell anyone who is interested: "Hey, something happened, here is the data."
In C++, we achieve this by creating an abstract base class—an interface. Here is how I usually structure this to keep it clean:
#include <vector>
#include <algorithm>
#include <iostream>
// The "Contract"
class IObserver {
public:
virtual ~IObserver() = default;
virtual void onNotify(int value) = 0;
};
// The Broadcaster
class Subject {
std::vector<IObserver*> observers;
public:
void addObserver(IObserver* observer) {
observers.push_back(observer);
}
void removeObserver(IObserver* observer) {
// Use the erase-remove idiom to keep the list clean
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notify(int value) {
for (auto* observer : observers) {
observer->onNotify(value);
}
}
};
// Concrete implementation 1: The UI
class HealthBar : public IObserver {
public:
void onNotify(int value) override {
std::cout < "UI: Updating health bar to " < value < "%" < std::endl;
}
};
// Concrete implementation 2: The Sound System
class SoundEngine : public IObserver {
public:
void onNotify(int value) override {
if (value < 20) {
std::cout < "Sound: Playing 'Low Health' warning beep!" < std::endl;
}
}
};
Now, look at the Subject class. Does it mention HealthBar? Does it include SoundEngine.h? No. It doesn't care. You can add a hundred different observers—logging systems, analytics trackers, particle effects—and you will never have to touch a single line of code inside the Subject class again. That is the power of this pattern.
One quick word of caution: in a real production environment, you have to be very careful about the lifetime of your observers. If a HealthBar object is destroyed but forgot to call removeObserver(), the Subject will eventually try to call onNotify() on a dangling pointer, and your program will crash. In a more advanced system, I'd suggest using std::weak_ptr, but for this exercise, we'll stick to raw pointers to keep the logic focused on the pattern itself.
📋 Practical Task
Implementation Exercise: Building a Stock Market Alert System
To put this into practice, you're going to build a simple Stock Market simulator. You need to implement an event system where a StockTicker (the Subject) notifies various Investor types (the Observers) whenever a stock price changes.
Requirements:
- Create an
IStockObserverinterface with a methodupdatePrice(std::string symbol, double price). - Implement a
StockTickerclass that:- Maintains a list of observers.
- Has methods to
attach()anddetach()observers. - Has a
setPrice(std::string symbol, double price)method that triggers the notification to all attached observers.
- Implement two concrete observers:
MobileApp: Prints a message like "Push Notification: [Symbol] is now $[Price]".DayTrader: Only prints a message if the price drops below a certain threshold (e.g., $100.00), indicating a "Buy" opportunity.
Test Case: In your main(), create one StockTicker, attach one MobileApp and one DayTrader, and call setPrice("AAPL", 150.00) and then setPrice("AAPL", 95.00). Verify that the DayTrader only reacts to the second update.
There are no comments for now.