Let us assume we have some class (
Subject) with
properties that can be changed. A set of current values of all properties defines Subject's
state. Another class (
Observer) must be notified each time Subject's state changes (each time some property changes).
Observer pattern decouples direct dependencies between (concrete) subject and (concrete) observer by using interfaces.
The following code shows classic (
Gang of Four) implementation of the Observer pattern applied to two subjects (
PropertyOwner1 and
PropertyOwner2) and their observers (
PropertyObserver1 and
PropertyObserver2):
main.cpp:
Output:
PropertyOwner1: property1's new value is: 1
PropertyObserver1: PropertyOwner1 property1's value is: 1
PropertyObserver2: PropertyOwner1 property2's value is: 0
PropertyOwner1: property2's new value is: 1.2
PropertyObserver1: PropertyOwner1 property1's value is: 1
PropertyObserver2: PropertyOwner1 property2's value is: 1.2
PropertyOwner2: property1's new value is: 1
PropertyObserver1: PropertyOwner2 property2's value is: 0
PropertyObserver2: PropertyOwner2 property1's value is: 1
PropertyOwner2: property2's new value is: 3.4
PropertyObserver1: PropertyOwner2 property2's value is: 3.4
PropertyObserver2: PropertyOwner2 property1's value is: 1
PropertyOwner1: property1's new value is: 2
PropertyObserver2: PropertyOwner1 property2's value is: 1.2
PropertyOwner1: property2's new value is: 4.5
PropertyObserver2: PropertyOwner1 property2's value is: 4.5
As we can see, observer is notified each time
any property of the subject changes. E.g. if we look the test where PropertyOwner1's property1 changes to
2 but PropertyObserver2 is notified regardless the fact that it is interested only in PropertyOwner1's property2.
In this model client gets information that the subject's state (
some property) has been changed but it does not know exactly
which property has been changed. Of course, observer could maintain the history of obtained values for each property and compare new values with the previous ones and thus detect for which property the value has changed but this increases complexity of the observer.
Somehow we need to pass to the observer information about which property has changed. If we assign each property in a system a unique ID we could pass it to the observer as an additional parameter of the
Subject::update() method:
main.cpp:
#include
#include
#include
class Subject;
enum PropertyID
{
Property1,
Property2
};
class Observer
{
public:
virtual ~Observer(){}
// pointer to Subject is passed so ConcreteObserver can distinct ConcreteSubjects
virtual void update(Subject*, PropertyID) = 0;
};
class Subject
{
public:
virtual ~Subject(){}
void registerObserver(Observer* const o)
{
std::list::const_iterator it = std::find(observers_.begin(), observers_.end(), o);
if(it != observers_.end())
throw std::runtime_error("Observer already registered");
observers_.push_back(o);
}
void unregisterObserver(Observer* const o)
{
std::list::const_iterator it = std::find(observers_.begin(), observers_.end(), o);
if(it != observers_.end())
observers_.remove(o);
}
void notify(PropertyID propertyID)
{
std::list::const_iterator it = observers_.begin();
for(; it != observers_.end(); ++it)
(*it)->update(this, propertyID);
}
protected:
std::list observers_;
};
// Concrete Subject
class PropertyOwner1 : public Subject
{
int property1_;
float property2_;
public:
PropertyOwner1() : property1_(0), property2_(0.0f){}
void property1(int n)
{
if(n != property1_)
{
property1_ = n;
std::cout << "\nPropertyOwner1: property1's new value is: " << property1_ << std::endl; notify(Property1); } } int property1() const { return property1_;} void property2(float n) { if(n != property2_) { property2_ = n; std::cout << "\nPropertyOwner1: property2's new value is: " << property2_ << std::endl; notify(Property2); } } float property2() const { return property2_;} }; class PropertyOwner2 : public Subject { bool property1_; double property2_; public: PropertyOwner2() : property1_(false), property2_(0.0){} void property1(bool n) { if(n != property1_) { property1_ = n; std::cout << "\nPropertyOwner2: property1's new value is: " << property1_ << std::endl; notify(Property1); } } bool property1() const { return property1_;} void property2(double n) { if(n != property2_) { property2_ = n; std::cout << "\nPropertyOwner2: property2's new value is: " << property2_ << std::endl; notify(Property2); } } double property2() const { return property2_;} }; // Concrete Observer // observes changes in property1 of ConcreteSubject1 and // property2 of ConcreteSubject2 class PropertyObserver1 : public Observer { // ConcreteObserver knows about ConcreteSubjects PropertyOwner1* pPropertyOwner1_; PropertyOwner2* pPropertyOwner2_; public: PropertyObserver1(PropertyOwner1* pConcreteSubject1, PropertyOwner2* pPropertyOwner2) : pPropertyOwner1_(pConcreteSubject1), pPropertyOwner2_(pPropertyOwner2){} void update(Subject* pSubject, PropertyID propertyID) { if(pSubject == pPropertyOwner1_) { if(propertyID == Property1) { int property1 = pPropertyOwner1_->property1();
std::cout << "\tPropertyObserver1: PropertyOwner1 property1's value is: " << property1 << std::endl; } } else if(pSubject == pPropertyOwner2_) { if(propertyID == Property2) { double property2 = pPropertyOwner2_->property2();
std::cout << "\tPropertyObserver1: PropertyOwner2 property2's value is: " << property2 << std::endl; } } } }; // Concrete Observer // observes changes in property2 of ConcreteSubject1 and // property1 of ConcreteSubject2 class PropertyObserver2 : public Observer { PropertyOwner1* pPropertyOwner1_; PropertyOwner2* pPropertyOwner2_; public: PropertyObserver2(PropertyOwner1* pPropertyOwner1, PropertyOwner2* pPropertyOwner2) : pPropertyOwner1_(pPropertyOwner1), pPropertyOwner2_(pPropertyOwner2){} void update(Subject* pSubject, PropertyID propertyID) { if(pSubject == pPropertyOwner1_) { if(propertyID == Property2) { float property2 = pPropertyOwner1_->property2();
std::cout << "\tPropertyObserver2: PropertyOwner1 property2's value is: " << property2 << std::endl; } } else if(pSubject == pPropertyOwner2_) { if(propertyID == Property1) { bool property1 = pPropertyOwner2_->property1();
std::cout << "\tPropertyObserver2: PropertyOwner2 property1's value is: " << property1 << std::endl; } } } };
Output shows that this time each observer knew exactly which property of which subject has been changed:
PropertyOwner1: property1's new value is: 1
PropertyObserver1: PropertyOwner1 property1's value is: 1
PropertyOwner1: property2's new value is: 1.2
PropertyObserver2: PropertyOwner1 property2's value is: 1.2
PropertyOwner2: property1's new value is: 1
PropertyObserver2: PropertyOwner2 property1's value is: 1
PropertyOwner2: property2's new value is: 3.4
PropertyObserver1: PropertyOwner2 property2's value is: 3.4
PropertyOwner1: property1's new value is: 2
PropertyOwner1: property2's new value is: 4.5
PropertyObserver2: PropertyOwner1 property2's value is: 4.5
There is a room for further improvements:
notify() (and so observer's
update()) function is called each time any property of the subject is changed. Yes, Property ID is passed so observer knows which property has been modified but still - can we avoid calling
update() of the observer that is not interested in that particular property?
The solution is to granulate Observer pattern: instead of taking PropertyOwners as concrete subjects, let us move Observer pattern one level down and take PropertyOwners' properties as
explicit concrete subjects. This implies that observers will need to register with properties, not with their owning classes - PropertyOwners. Each property will implement Subject interface and maintain its list of observers. When calling notify()/update() it needs to pass information on the type of its owner (PropertyOwner -
"implicit concrete subject") so the observer knows which PropertyOwner has changed. On the observer's side we can do the same, granulate the pattern to smaller units which belong to particular class (PropertyObserverOwner -
"implicit concrete observer") and which observe particular
property of the particular class. Templates come handy for this implementation:
main.cpp:
#include
#include
#include
// Abstract Observer
template
class PropertyObserver
{
public:
virtual void update(const TPropertyType& newValue) = 0;
};
// Concrete Observer (member of the class that observes a set of properties through a set of
// property concrete observers)
template
class PropertyObserverMember : public PropertyObserver
{
TPropertyObserverOwner* pPropertyObserverOwner_;
typedef void (TPropertyObserverOwner::*TPropertyChangeHandler)(const TPropertyType&);
TPropertyChangeHandler handler_;
public:
PropertyObserverMember(TPropertyObserverOwner* pPropertyObserverOwner, TPropertyChangeHandler handler) :
pPropertyObserverOwner_(pPropertyObserverOwner), handler_(handler){}
void update(const TPropertyType& newValue)
{
(pPropertyObserverOwner_->*handler_)(newValue);
}
};
// Subject
template
class ObservableProperty
{
public:
virtual ~ObservableProperty(){};
void registerObserver(PropertyObserver* const o)
{
std::list* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), o);
if(it != observers_.end())
throw std::runtime_error("Observer already registered");
observers_.push_back(o);
}
void unregisterObserver(PropertyObserver* const o)
{
std::list* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), o);
if(it != observers_.end())
observers_.remove(o);
}
void notify(const TPropertyType& newValue)
{
std::list* const>::const_iterator it = observers_.begin();
for(; it != observers_.end(); ++it)
(*it)->update(newValue);
}
protected:
std::list* const> observers_;
};
// Concrete Subject
template
class Property : public ObservableProperty
{
TPropertyType t_;
TPropertyOwner* owner_;
public:
Property(const TPropertyType& t, TPropertyOwner* owner) : t_(t), owner_(owner){}
void set(const TPropertyType& t)
{
if(t_ != t)
{
t_ = t;
std::cout << typeid(TPropertyOwner).name() << "'s property of type " << typeid(TPropertyType).name() << " has been set to " << t << std::endl; notify(t); } } TPropertyType get() const { return t_; } }; // class that owns concrete subjects struct PropertyOwner1 { Property property1;
Property property2;
//Property property3;
PropertyOwner1() :
property1(0, this),
property2(0.0f, this)/*,
property3("", this)*/
{}
};
// class that owns concrete subjects
struct PropertyOwner2
{
Property property1;
Property property2;
PropertyOwner2() :
property1(false, this),
property2(0.0, this)
{}
};
// class that owns concrete observers and through them
// observes changes in property1 of PropertyOwner1 and property2 of PropertyOwner2
struct PropertyObserverOwner1
{
PropertyObserverMember propertyOwner1_property1_Observer_;
PropertyObserverMember propertyOwner2_property2_Observer_;
//PropertyObserverMember propertyOwner1_property3_Observer_;
PropertyObserverOwner1() :
propertyOwner1_property1_Observer_(this, &PropertyObserverOwner1::OnPropertyOwner1Property1Changed),
propertyOwner2_property2_Observer_(this, &PropertyObserverOwner1::OnPropertyOwner2Property2Changed)/*,
propertyOwner1_property3_Observer_(this, &PropertyObserverOwner1::OnPropertyOwner1Property3Changed)*/
{}
void OnPropertyOwner1Property1Changed(const int& newValue)
{
std::cout << "\tPropertyObserverOwner1::OnPropertyOwner1Property1Changed(): \n\tnew value is: " << newValue << std::endl; } void OnPropertyOwner2Property2Changed(const double& newValue) { std::cout << "\tPropertyObserverOwner1::OnPropertyOwner2Property2Changed(): \n\tnew value is: " << newValue << std::endl; } /* void OnPropertyOwner1Property3Changed(const std::string& newValue) { std::cout << "\tPropertyObserverOwner1::OnPropertyOwner1Property3Changed(): \n\tnew value is: " << newValue << std::endl; } */ }; // class that owns concrete observers and through them // observes changes in property2 of PropertyOwner1 and property1 of PropertyOwner2 struct PropertyObserverOwner2 { PropertyObserverMember propertyOwner1_property2_Observer_;
PropertyObserverMember propertyOwner2_property1_Observer_;
PropertyObserverOwner2() :
propertyOwner1_property2_Observer_(this, &PropertyObserverOwner2::OnPropertyOwner1Property2Changed),
propertyOwner2_property1_Observer_(this, &PropertyObserverOwner2::OnPropertyOwner2Property1Changed){}
void OnPropertyOwner1Property2Changed(const float& newValue)
{
std::cout << "\tPropertyObserverOwner2::OnPropertyOwner1Property2Changed(): \n\tnew value is: " << newValue << std::endl; } void OnPropertyOwner2Property1Changed(const bool& newValue) { std::cout << "\tPropertyObserverOwner2::OnPropertyOwner2Property1Changed() \n\tnew value is: " << newValue << std::endl; } }; int main(int argc, char** argv) { PropertyOwner1 propertyOwner1; PropertyOwner2 propertyOwner2; PropertyObserverOwner1 propertyObserverOwner1; PropertyObserverOwner2 propertyObserverOwner2; // register propertyObserverOwner1's observers propertyOwner1.property1.registerObserver(&propertyObserverOwner1.propertyOwner1_property1_Observer_); propertyOwner2.property2.registerObserver(&propertyObserverOwner1.propertyOwner2_property2_Observer_); // register propertyObserverOwner2's observers propertyOwner1.property2.registerObserver(&propertyObserverOwner2.propertyOwner1_property2_Observer_); propertyOwner2.property1.registerObserver(&propertyObserverOwner2.propertyOwner2_property1_Observer_); propertyOwner1.property1.set(1); propertyOwner1.property2.set(1.2f); propertyOwner2.property1.set(true); propertyOwner2.property2.set(3.4); // unregister propertyObserverOwner1's observers propertyOwner1.property1.unregisterObserver(&propertyObserverOwner1.propertyOwner1_property1_Observer_); propertyOwner2.property2.unregisterObserver(&propertyObserverOwner1.propertyOwner2_property2_Observer_); propertyOwner1.property1.set(2); propertyOwner1.property2.set(4.5f); }
Output shows that this time a single update() call is made for a single property change:
struct PropertyOwner1's property of type int has been set to 1
PropertyObserverOwner1::OnPropertyOwner1Property1Changed():
new value is: 1
struct PropertyOwner1's property of type float has been set to 1.2
PropertyObserverOwner2::OnPropertyOwner1Property2Changed():
new value is: 1.2
struct PropertyOwner2's property of type bool has been set to 1
PropertyObserverOwner2::OnPropertyOwner2Property1Changed()
new value is: 1
struct PropertyOwner2's property of type double has been set to 3.4
PropertyObserverOwner1::OnPropertyOwner2Property2Changed():
new value is: 3.4
struct PropertyOwner1's property of type int has been set to 2
struct PropertyOwner1's property of type float has been set to 4.5
PropertyObserverOwner2::OnPropertyOwner1Property2Changed():
new value is: 4.5
I don't like the previous implementation. It is too complex and its scalability is questionable.
I was always a big fan of C# events and delegates. It is so easy to bind some event with its handler. I tried to implement something similar in C++ and here is the result:
main.cpp:
#include
#include
#include
// use base class to resolve the problem of how to put into collection objects of different types
template
struct PropertyChangedDelegateBase
{
virtual ~PropertyChangedDelegateBase(){};
virtual void operator()(const TPropertyType& t) = 0;
};
template
struct PropertyChangedDelegate : public PropertyChangedDelegateBase
{
THandlerOwner* pHandlerOwner_;
typedef void (THandlerOwner::*TPropertyChangeHandler)(const TPropertyType&);
TPropertyChangeHandler handler_;
public:
PropertyChangedDelegate(THandlerOwner* pHandlerOwner, TPropertyChangeHandler handler) :
pHandlerOwner_(pHandlerOwner), handler_(handler){}
void operator()(const TPropertyType& t)
{
(pHandlerOwner_->*handler_)(t);
}
};
template
class PropertyChangedEvent
{
public:
virtual ~PropertyChangedEvent(){};
void add(PropertyChangedDelegateBase* const d)
{
std::list* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if(it != observers_.end())
throw std::runtime_error("Observer already registered");
observers_.push_back(d);
}
void remove(PropertyChangedDelegateBase* const d)
{
std::list* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if(it != observers_.end())
observers_.remove(d);
}
// notify
void operator()(const TPropertyType& newValue)
{
std::list* const>::const_iterator it = observers_.begin();
for(; it != observers_.end(); ++it)
{
(*it)->operator()(newValue);
}
}
protected:
std::list* const> observers_;
};
// class that owns concrete subjects
class PropertyOwner1
{
int property1_;
float property2_;
public:
PropertyChangedEvent property1ChangedEvent;
PropertyChangedEvent property2ChangedEvent;
PropertyOwner1() :
property1_(0),
property2_(0.0f)
{}
int property1() const {return property1_;}
void property1(int n)
{
if(property1_ != n)
{
property1_ = n;
std::cout << "PropertyOwner1::property1(): property1_ set to " << property1_ << std::endl; property1ChangedEvent(property1_); } } float property2() const {return property2_;} void property2(float n) { if(property2_ != n) { property2_ = n; std::cout << "PropertyOwner1::property2(): property2_ set to " << property2_ << std::endl; property2ChangedEvent(property2_); } } }; // class that owns concrete subjects class PropertyOwner2 { bool property1_; double property2_; public: PropertyChangedEvent property1ChangedEvent;
PropertyChangedEvent property2ChangedEvent;
PropertyOwner2() :
property1_(false),
property2_(0.0)
{}
bool property1() const {return property1_;}
void property1(bool n)
{
if(property1_ != n)
{
property1_ = n;
std::cout << "PropertyOwner2::property1(): property1_ set to " << property1_ << std::endl; property1ChangedEvent(property1_); } } double property2() const {return property2_;} void property2(double n) { if(property2_ != n) { property2_ = n; std::cout << "PropertyOwner2::property2(): property2_ set to " << property2_ << std::endl; property2ChangedEvent(property2_); } } }; // class that observes changes in property1 of PropertyOwner1 and property1 of PropertyOwner2 struct PropertyObserver1 { void OnPropertyOwner1Property1Changed(const int& newValue) { std::cout << "\tPropertyObserver1::OnPropertyOwner1Property1Changed(): \n\tnew value is: " << newValue << std::endl; } void OnPropertyOwner2Property1Changed(const bool& newValue) { std::cout << "\tPropertyObserver1::OnPropertyOwner2Property1Changed(): \n\tnew value is: " << newValue << std::endl; } }; // class that observes changes in property2 of PropertyOwner1 and property2 of PropertyOwner2 struct PropertyObserver2 { void OnPropertyOwner1Property2Changed(const float& newValue) { std::cout << "\tPropertyObserver2::OnPropertyOwner1Property2Changed(): \n\tnew value is: " << newValue << std::endl; } void OnPropertyOwner2Property2Changed(const double& newValue) { std::cout << "\tPropertyObserver2::OnPropertyOwner2Property2Changed(): \n\tnew value is: " << newValue << std::endl; } }; int main(int argc, char** argv) { PropertyOwner1 propertyOwner1; PropertyOwner2 propertyOwner2; PropertyObserver1 propertyObserver1; PropertyObserver2 propertyObserver2; // register observers PropertyChangedDelegate delegate1(&propertyObserver1, &PropertyObserver1::OnPropertyOwner1Property1Changed);
propertyOwner1.property1ChangedEvent.add(&delegate1);
PropertyChangedDelegate delegate2(&propertyObserver2, &PropertyObserver2::OnPropertyOwner1Property2Changed);
propertyOwner1.property2ChangedEvent.add(&delegate2);
PropertyChangedDelegate delegate3(&propertyObserver1, &PropertyObserver1::OnPropertyOwner2Property1Changed);
propertyOwner2.property1ChangedEvent.add(&delegate3);
PropertyChangedDelegate delegate4(&propertyObserver2, &PropertyObserver2::OnPropertyOwner2Property2Changed);
propertyOwner2.property2ChangedEvent.add(&delegate4);
propertyOwner1.property1(1);
propertyOwner1.property2(1.2f);
propertyOwner2.property1(true);
propertyOwner2.property2(3.4);
// unregister PropertyObserver1
propertyOwner1.property1ChangedEvent.remove(&delegate1);
propertyOwner2.property1ChangedEvent.remove(&delegate3);
propertyOwner1.property1(2);
propertyOwner1.property2(4.5f);
}
Output:
PropertyOwner1::property1(): property1_ set to 1
PropertyObserver1::OnPropertyOwner1Property1Changed():
new value is: 1
PropertyOwner1::property2(): property2_ set to 1.2
PropertyObserver2::OnPropertyOwner1Property2Changed():
new value is: 1.2
PropertyOwner2::property1(): property1_ set to 1
PropertyObserver1::OnPropertyOwner2Property1Changed():
new value is: 1
PropertyOwner2::property2(): property2_ set to 3.4
PropertyObserver2::OnPropertyOwner2Property2Changed():
new value is: 3.4
PropertyOwner1::property1(): property1_ set to 2
PropertyOwner1::property2(): property2_ set to 4.5
PropertyObserver2::OnPropertyOwner1Property2Changed():
new value is: 4.5
This is exactly what I wanted: observer registered with particular property (event), notified when property's changed and with a knowledge of property's owner and a new value.