Definition
The observer pattern defines a one-to-many dependency between objects. So that when it (the observable) changes its state, all of its dependencies are notified.
Scenario
Instead of making observer keep asking “if there is an update in some data” (polling), we make the system push when itself has changed.
This also helps separating or decoupling methods of objects that trigger an event and methods of objects that should react to the event.
Class Diagram
Observer can subscribe to observable using Add
or Remove
. When the observable changes, it calls Notify
, which loops through a list of subscribed observer and calls their Update
method.
There are two kinds of diagram for pulling data from ConcreteObservable
. We can either expose GetData
method in ConcreteObservable
and let ConcreteObserver
pull the data on their own (Method 1) or pass it in Update
method (Method 2).
The diagram draws the first method, and the implementation shows the second method.
Method 1 – Make observer has knowledge of observerable
Most of the time, we want to make dependency relation between interfaces not concretion, so this is an exception.
We make ConcreteObserver
has a ConcreteObservable
, so that it call pull data itself by calling method defined inside ConcreteObservale
when being notified by Notify
.
Method 2 – Passing data through function
We can simply pass data to Update
and get information.
Used Cases
- In GUI components, they can react to events happening in other objects without coupling to their classes.
- A real life example in
lumino
ISignal
interface.
Questions
Can observer patterns use for out-of-process database?
Design patterns are about how the code is structured within the program.1 So we might want the observable to watch the database and notify the observer if it changes.
Or we may need service oriented architecture.
Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// observe.h
#ifndef __OBSERVE_H__
#define __OBSERVE_H__
#include <vector>
class IObserver {
public:
virtual void Update(double temperature) = 0;
};
class IObservable {
protected:
std::vector<IObserver*> observer_list_;
public:
virtual void Add(IObserver*) = 0;
virtual void Remove(IObserver*) = 0;
virtual void Notify() = 0;
};
class WeatherStation : public IObservable {
private:
double temperature_;
public:
void Add(IObserver* observer) override;
void Remove(IObserver* observer) override;
void Notify() override;
void GetTemperature();
void Run();
};
class PhoneDisplay : public IObserver {
public:
void Update(double temperature) override;
};
class TabletDisplay : public IObserver {
public:
void Update(double temperature) override;
};
#endif //__OBSERVE_H__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// observe.cpp
#include "observe.h"
#include <iostream>
void WeatherStation::Add(IObserver* observer) { observer_list_.emplace_back(observer); }
void WeatherStation::Remove(IObserver* observer) {
for (unsigned int i = 0; i < observer_list_.size(); i++) {
if (observer_list_[i] == observer) {
observer_list_.erase(observer_list_.begin() + i);
}
}
}
void WeatherStation::Notify() {
for (auto o : observer_list_) {
o->Update(temperature_);
}
}
void WeatherStation::GetTemperature() { temperature_ = 1.0; }
void WeatherStation::Run() {
GetTemperature();
Notify();
}
void PhoneDisplay::Update(double temperature) {
std::cout << "[PhoneDisplay] Updating temperature = " << temperature << std::endl;
}
void TabletDisplay::Update(double temperature) {
std::cout << "[TabletDisplay] Updating temperature = " << temperature << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.cpp
#include "observe.h"
int main() {
PhoneDisplay* phone_display = new PhoneDisplay();
TabletDisplay* tablet_display = new TabletDisplay();
WeatherStation weather_station;
weather_station.Add(phone_display);
weather_station.Add(tablet_display);
weather_station.Run(); // some logic that makes weather station want to push data
delete phone_display;
delete tablet_display;
return 0;
}