# Observer
One of the most popular design patterns is an observer. Almost every project uses at least one observer. This popular pattern is used to notify other classes about some changes. There are two types of observers:
* Pull observer: We inform a class about change, but this class needs to find out what changed
* Push observer: We send all necessary information to class about what changes
A first approach is more flexible, but forcing a class to get info on what changed, may take a long time to get this info, which may cause an observer slower
A second approach is less flexible because we need to create a method per change, to be sure, that the other class will know what happens and can use changed values without explicitly taking arguments or checking the state from the observed class
___
The observer design pattern is a behavioural pattern listed among the 23 well-known "Gang of Four" design patterns that address recurring design challenges in order to design flexible and reusable object-oriented software, yielding objects that are easier to implement, change, test and reuse. Observer has the following traits:
* A one-to-many dependency between objects should be defined without making the objects tightly coupled.
* When one object changes state, an open-ended number of dependent objects should be updated automatically.
* An object can notify multiple other objects
___
## Simple example
We want to inform other classes when the browser will change the focus or will be closed.
```C++
struct Observer {
virtual ~Observer() = default;
virtual void browserAboutToQuit() = 0;
virtual void browserFocusChanged() = 0;
}
```
___
```C++
class Browser {
public:
struct Observer { /* implementation */ };
~Browser() {
std::cout << "Browser is closing\n";
notifyOnQuit();
std::cout << "Browser closed\n";
}
// Rule of 5 :)
void attach(Observer* observer) { _observers.push_back(observer); }
void detach(Observer* observer) { std::erase(_observers, observer); }
private:
void notifyOnQuit() const {
std::ranges::for_each(_observers, std::mem_fn(&Observer::browserAboutToQuit));
}
void notifyOnFocusChanfed() const {
std::ranges::for_each(_observers, std::mem_fn(&Observer::browserFocusChanged));
}
std::vector _observers;
};
```
___
```C++
class TabStripManager : public Browser::Observer {
public:
TabStripManager(Browser* browser): _browser(browser) {
browser->attach(this);
}
~TabStripManager() {
_browser->detach(this);
}
// Rule of 5 :)
void browserAboutToQuit() override {
std::cout << "TabStripManager will close tabs\n";
}
void browserFocusChanged() override {
std::cout << "TabStripManager lost focus\n";
}
private:
Browser* _browser;
};
```
___
```C++
int main() {
std::unique_ptr browser = std::make_unique();
TabStripManager manager(browser.get());
browser = nullptr;
}
```
```bash
Browser is closing
TabStripManager will close tabs
Browser closed
```
___
## Problem with observer
Let's check once more the class `TabStripManager`, did you spot a problem here?
```C++
class TabStripManager : public Browser::Observer {
public:
TabStripManager(Browser* browser): _browser(browser) {
browser->attach(this);
}
~TabStripManager() {
_browser->detach(this);
}
// Rule of 5 :)
void browserAboutToQuit() override {
std::cout << "TabStripManager will close tabs\n";
}
void browserFocusChanged() override {
std::cout << "TabStripManager lost focus\n";
}
private:
Browser* _browser;
};
```
___
## About to quit
Observer is a useful mechanism to inform other classes that hold a pointer to our class to invalidate this pointer because we are destroying it. If we forget to invalidate the observer we will call` detach` in destructor which will cause a UB because we hold a dangling pointer.
```C++
class TabStripManager : public Browser::Observer {
public:
TabStripManager(Browser* browser): _browser(browser) {
assert(browser);
browser->attach(this);
}
~TabStripManager() {
if (_browser) {
_browser->detach(this);
}
}
// Rule of 5 :)
void browserAboutToQuit() override {
std::cout << "TabStripManager will close tabs\n";
_browser = nullptr;
}
void browserFocusChanged() override {
std::cout << "TabStripManager lost focus\n";
}
private:
Browser* _browser;
};
```
___
## Template observer
Do we always need to copy and paste the `Observer` class to every other class that wants to use the pattern? And do we always need to add a method for every state? Can we do this without duplicating the code?
Spoiler was in the topic, yes we can!
```C++
template
struct Observer {
virtual ~Observer() = default;
virtual void update(State state) = 0;
};
```
___
```C++
class Browser {
public:
enum class State {
Closing,
FocusChanged
};
using BrowserObserver = Observer;
~Browser() {
std::cout << "Browser is closing\n";
notify(State::Closing);
std::cout << "Browser closed\n";
}
// Rule of 5 :)
void attach(BrowserObserver* observer) { _observers.push_back(observer); }
void detach(BrowserObserver* observer) { std::erase(_observers, observer); }
private:
void notify(State state) const {
std::ranges::for_each(_observers, [state](auto* observer){
observer->update(state);
});
}
std::vector _observers;
};
```
___
```C++
class TabStripManager : public Browser::BrowserObserver {
public:
TabStripManager(Browser* browser): _browser(browser) {
assert(browser);
browser->attach(this);
}
~TabStripManager() {
if (_browser) {
_browser->detach(this);
}
}
// Rule of 5 :)
// dispatch message
void update(Browser::State state) override {
switch (state) {
case Browser::State::Closing: {
browserAboutToQuit();
return;
}
case Browser::State::FocusChanged: {
browserFocusChanged();
return;
}
}
}
private:
void browserAboutToQuit() {
std::cout << "TabStripManager will close tabs\n";
_browser = nullptr;
}
void browserFocusChanged() {
std::cout << "TabStripManager lost focus\n";
}
Browser* _browser;
};
```
___
## Still we have the same behavior
```C++
int main() {
std::unique_ptr browser = std::make_unique();
TabStripManager manager(browser.get());
browser = nullptr;
}
```
```bash
Browser is closing
TabStripManager will close tabs
Browser closed
```
___
## Problem with observer part 2
Let's check once more the class `Browser` do you spot any problem here?
```C++
class Browser {
public:
enum class State {
Closing,
FocusChanged
};
using BrowserObserver = Observer;
~Browser() {
std::cout << "Browser is closing\n";
notify(State::Closing);
std::cout << "Browser closed\n";
}
// Rule of 5 :)
void attach(BrowserObserver* observer) { _observers.push_back(observer); }
void detach(BrowserObserver* observer) { std::erase(_observers, observer); }
private:
void notify(State state) const {
std::ranges::for_each(_observers, [state](auto* observer){
observer->update(state);
});
}
std::vector _observers;
};
```
___
## Attach / dettach
Question1: How do we bahve, when someone will attach twice the same observer?
- 1) Register it twice, and than notify twice? Like `std::recursive_mutex`?
- 2) Throw an exception?
- 3) Return an error?
- 4) Ignore it, and only print some warning logs about that?
The answer is not that easy, as usual, it depends, on what we want to achieve
Question2: How do we bahve, when someone will detach twice the same observer?
- 1) Check if someone attach it twice?
- 2) Throw an exception?
- 3) Return an error?
- 4) Ignore it, and only print some warning logs about that?
I guess you know the answer :) it depens.
___
## Problem with observer part 3
This is not all the problems that we may have with the observer, let's check this snippet of code. Do you spot any problem here?
```C++
class Browser {
public:
enum class State {
Closing,
FocusChanged
};
using BrowserObserver = Observer;
void attach(BrowserObserver* observer) { _observers.emplace(observer); }
void detach(BrowserObserver* observer) { _observers.erase(observer); }
void setFocus(bool focus) {
if (std::exchange(_hasFocus, focus) != focus) {
notify(State::FocusChanged);
}
}
bool hasFocus() const { return _hasFocus; }
private:
void notify(State state) const {
std::ranges::for_each(_observers, [state](auto* observer){
observer->update(state);
});
}
bool _hasFocus{false};
std::set _observers;
};
```
___
## Maybe now?
```C++
class Extension : public Browser::BrowserObserver {
public:
Extension(Browser* browser, const std::string& name): _browser(browser), _name(name) { browser->attach(this); }
// Rule of 5 :)
~Extension() { if (_browser) { _browser->detach(this); } }
// dispatch message
void update(Browser::State state) override {
switch (state) {
case Browser::State::Closing: return;
case Browser::State::FocusChanged: {
if (_browser->hasFocus()) { displayExtension(); }
return;
}
}
}
private:
void displayExtension() {
std::cout << "This is an extension: " << _name << '\n';
}
Browser* _browser;
std::string _name;
};
```
___
## Order of observer
What will be printed?
```C++
int main() {
std::unique_ptr browser = std::make_unique();
Extension extA(browser.get(), "A");
Extension extB(browser.get(), "B");
Extension extC(browser.get(), "C");
browser->setFocus(true);
}
```
The order is unknown because we use `std::set`. Generally, we should **never depend on the order of calling an observer**. Because in the future someone can change the implementation and the program will stop working!
___
## Exercise
* Go into directory Core and implement Time and TimeObserver class
* Class Time should have 4 methods:
* void attach(TimeObserver* observer); -> attach observer
* void detach(TimeObserver* observer); -> detach observer
* Time& operator++(); -> increment day and notify observers
* size_t day() const; -> return current day
* Go into directory Ship and make Ship class inherit from TimeObserver
* Each time the observer will be triggered, you should give the crew food and drink
* Each crew member should consume 1 rum and 1 banana (I know they usually eat biscuits)
* If you don't have enough cargo crew should rebel
* Each day of a rebel you should subtract 5 members of the crew
* If the number of crew drops below 0, throw an exception "Game Over"
___
## Exercise 2
* Go into directory Store and make Store class inherit from *TimeObserver*
* Class Store should don't know anything about class Time
* We should attach and detach observer outside of class. Think about how you can make it exception-safe (RAII)
* Each time the observer will be triggered, you should change the number of available cargo in the store
* Each time draw a number between -50 and 50 and add it to the amount of cargo
* Print everyday cargo from the Shop and verify if the amount changes
___
## Moder apporach
In modern C++ we try to avoid too many references or pointers because they are problematic. We will try to implement an observer pattern by using value semantics. First let's get rid of the virtual function, and forcing classes to inherit from the observer class.
```C++
template
struct Observer {
// First, we don't need to use polymorphism, so also virtual D'tor is redundant
using OnUpdate = std::function;
explciit Observer(OnUpdate fun): _onUpdate(std::move(fun)) {}
void update(const Subject& subject, State state) {
std::invoke(_onUpdate, subject, state);
}
private:
OnUpdate _onUpdate;
};
```
___
Now move out of the `Browser` class `BorwserObserver` so the files that want to use `BrowserObserver` don't need to include the whole `Browser` file in the header. Only in the source file we will need a `Browser` library, so every time the `Browser` class changes we don't need to recompile a lot of files.
```C++
class Browser;
enum class BrowserState {
Closing,
FocusChanged
};
using BrowserObserver = Observer;
```
___
We will still use pointer as `Observer` because they are easy to use (we know that each address is unique) but we can change it to some `uuid` or sth similar to get rid of the pointer also in a `Borwser` class.
```C++
class Browser {
public:
void attach(BrowserObserver* observer) { _observers.emplace(observer); }
void detach(BrowserObserver* observer) { _observers.erase(observer); }
void setFocus(bool focus) {
if (std::exchange(_hasFocus, focus) != focus) {
notify(BrowserState::FocusChanged);
}
}
bool hasFocus() const { return _hasFocus; }
private:
void notify(BrowserState state) const {
std::ranges::for_each(_observers, [state, this](auto* observer){
observer->update(*this, state);
});
}
bool _hasFocus{false};
std::set _observers;
};
```
___
Extension class doesn't need to inherit from `Observer` so we get rid of destructor and vtable. Another advantage is that we separate observers from extension classes. Now it will be a separate object.
```C++
class Extension {
public:
explicit Extension(const std::string& name): _name(name) { }
// D'tor is not needed neither the rule of 5
// dispatch message
void update(const Browser& browser, BrowserState state) {
switch (state) {
case BrowserState::Closing:
return;
case BrowserState::FocusChanged: {
if (browser.hasFocus()) {
displayExtension();
}
return;
}
}
}
private:
void displayExtension() {
std::cout << "This is an extension: " << _name << '\n';
}
std::string _name;
};
```
___
We have three separate objects, one is a `Browser` the second is an `Extension` and the third is a `BrowserObserver`. We still can use traditional observer patterns, where we use inheritance, but in a lot of cases, we can avoid virtual and make our code faster.
```C++
int main() {
std::unique_ptr browser = std::make_unique();
Extension extension1("A");
Extension extension2("B");
Extension extension3("C");
BrowserObserver observer1([&extension1](const Browser& browser, BrowserState state){
extension1.update(browser, state);
});
BrowserObserver observer2([&extension2](const Browser& browser, BrowserState state){
extension2.update(browser, state);
});
BrowserObserver observer3([&extension3](const Browser& browser, BrowserState state){
extension3.update(browser, state);
});
browser->attach(&observer1);
browser->attach(&observer2);
browser->attach(&observer3);
browser->setFocus(true);
}
```
___
## Benchmark
By using value semantics, we may also get around 10% faster code. This depends on what we do as an observer, it the action is long, we will not see any difference, but for quick actions we may improve a performance.
___
## The last drawback of Oobserver
Please don't use it everywhere, because your code becomes more complicated when you will have hundreds of observers. Remember you can't relay on the order of notification, so if class `A` calls `notify` which triggers class `B` which also triggers a notification for class `C` which also triggers a notification for class `D` you will have really complicated structure and finding bugs and fixing it will be hard.
___
## Exercise 3
* Rewrite Observer to use modern approach -> by value semantic.
* Rewrite class Ship
* Rewrite class Store
* Question: Which class was easier to rewrite?