This concept and its many different variants is the most important notion you need to understand to produce good UI code.
If you apply this pattern everywhere you win, it's not more complicated than this. If however, you break this basic design, you will make life very complicated for those who come after you.
Behavior:
Analysis:
The system has no dependencies to UI. This is good because we can change the UI without changing the system. UI changes are a reality, make everyone’s life better and try to keep the UI code self-contained.
The UI always reflects the state of the system. The UI has no state itself. This means the UI and the system are always in sync.
All changes are taking effect on the system. This means whether changes come from the frontend or backend, or whether there are several frontends, or sources of change don't matter. The UI(s) are always up to date.
Observer patterns don’t form a loop, notifications only go one way which starts at the system and end at the view. Observer loops otherwise known as notification hell are extremely hard to fix and are always a symptom of bad design.
Prefer using CCrySignal wherever you need to observe something. This is our own implementation of signals (see Qt signals, or boost::signal) which is much more flexible than writing your own observer system. Do not use a virtual interface for observers, it’s 2016, use a functional approach, hence CCrySignal.
This is in the "getting started" section because of common misconceptions and generally bad approach from programmers that are not used to modern UI programming. The goal of this is educational, but it can also help recognize bad patterns in your own code.
//Including UI headers where they shouldn't be is a bad sign #include “SomeUIClass.h” void SetValue( const Value& val) { m_value = val; //This should not have a direct reference to the UI, but should simply call a signal to notify potential observers m_pUi->SetValue(val); } |
Solutions:
Encapsulate the fields that the UI reflects in setters/getters.
Use CCrySignal to notify observers within these accessors.
Observers can be UI or something else, and it shouldn’t matter to your system.
Just make sure it is observable, this will make your system able to be used by any UI system or another system in the future.
Notification loops create the need to guard for them. Here is a pseudocode example:
//Called by UI void SetValueFromUi( const Value& val) { m_value = val; m_bDoNotNotify = true ; //this will trigger the signal value changed which will call OnValueChanged() m_system->SetValue(val); m_bDoNotNotify = false ; } //Callback registered to signal on m_system void OnValueChanged( const Value& val) { //avoid updating value again here since we already did when the action changed if (m_bDoNotNotify) return ; m_value = val; } |
Problems:
Solutions: