Understanding the Observer Design Pattern in C++
The Observer pattern is a behavioral design pattern that establishes a one-to-many dependency relationship between objects. When the state of one object changes, all dependent objects are automatically notified and updated. This pattern is particularly useful in scenarios where multiple objects need to react to events occurring in another object, or when there's a need for reverse notification from member objects back to the main object.
Consider a scenario where employees are monitoring stock prices and entertainment content during work hours. When the manager returns to the office, a receptionist alertts all employees, who then immediately switch their screens back to work-related content. This real-world situation perfectly illustrates the Observer pattern in action.
Observer Interface
The observer defines a contract for objects that need to be notified of state changes:
class IEventListener
{
public:
virtual ~IEventListener() {}
virtual void handleEvent(const std::string& eventData) = 0;
};
Concrete Observer Implementation
Each concrete observer implements the notification interface and defines its own response behavior:
class ConcreteListener : public IEventListener
{
public:
ConcreteListener(const std::string& name) : m_name(name) {}
virtual ~ConcreteListener() {}
virtual void handleEvent(const std::string& eventData) override
{
std::cout << m_name << " received event: " << eventData << std::endl;
}
private:
std::string m_name;
};
Subject Base Class
The subject maintains a list of observers and provides methods to attach, detach, and notify them:
class Subject
{
public:
Subject() {}
virtual ~Subject() {}
bool attach(IEventListener* pListener)
{
if (pListener)
{
m_listeners.push_back(pListener);
return true;
}
return false;
}
void detach(IEventListener* pListener)
{
m_listeners.remove(pListener);
}
void notifyAll(const std::string& eventData)
{
if (!m_listeners.empty())
{
for (auto it = m_listeners.begin(); it != m_listeners.end(); ++it)
{
IEventListener* pListener = *it;
pListener->handleEvent(eventData);
}
}
}
private:
std::list<IEventListener*> m_listeners;
};
Concrete Subject
The concrete subject inherits from the base class and triggers notifications when specific events occur:
class Manager : public Subject
{
public:
void arrive()
{
std::cout << "Manager: I have arrived at the office" << std::endl;
notifyAll("Manager Arrived");
}
void depart()
{
std::cout << "Manager: Leaving the office now" << std::endl;
notifyAll("Manager Left");
}
};
Usage Example
void ObserverPatternDemo()
{
ConcreteListener listener1("Alice");
ConcreteListener listener2("Bob");
Manager officeManager;
officeManager.attach(&listener1);
officeManager.attach(&listener2);
officeManager.arrive();
// Remove listener1 after manager arrives
officeManager.detach(&listener1);
officeManager.depart();
}
Output:
Manager: I have arrived at the office
Alice received event: Manager Arrived
Bob received event: Manager Arrived
Manager: Leaving the office now
Bob received event: Manager Left
This pattern is widely used in various applications. For instance, when using messaging applications across multiple devices, incoming messages trigger notifications on all connected clients simultaneously. Each device acts as an observer, and the message server serves as the subject that broadcasts notifications to all subscribers.