So. Multiply-inheriting recursively templatized base classes is a bad idea.
We could put the list of
Subjects directly into the Observer class. Except that this is meant to be a reusable library, and we'd be
making the assumption that our customers don't mind having their Observers loaded with a member container
of Subject pointers. Certainly some customers won't mind that. But others would.
Especially those who know beforehand the destruction order of their objects, so they don't want the bloat.
This is where Andrei's policy-based design comes to the rescue. The Observer can be declared with a
policy that specifies whether it should auto-manage a list of subjects, or no, be a lean mean observing
machine, and the descendant class will make its own guarantees about lifespans.
The beauty of Andrei's policy-based design is that when you select the policy that suits you, you get
only what you ask for. The instantiated library classes are built of the template policy classes,
and so aren't loaded with state or features you didn't ask for.
Here's the default policy (and updated Observer) that monitors the lifespans of the observer and subject classes.
// The default policy
template < typename T >
class Monitored
{
public:
void RemoveAll(Observer<T, ::Monitored> *p);
void Add(Subject<T, ::Monitored> *p);
void Remove(Subject<T, ::Monitored> *p);
private:
std::list<Subject<T, ::Monitored> *> subjects_;
};
template < typename T, template <typename> class LifePolicy = Monitored>
class Observer {
public:
virtual ~Observer() { lifepolicy_.RemoveAll(this); }
virtual void Update(T hint) = 0;
private:
void AdviseAttach(Subject<T, LifePolicy> *p) { lifepolicy_.Add(p); }
void AdviseDetach(Subject<T, LifePolicy> *p) { lifepolicy_.Remove(p); }
LifePolicy<T> lifepolicy_;
friend class Subject<T, LifePolicy>;
};
And here's how a client would declare classes with the default Monitored policy.
The syntax for construction, attaching and notifying objects can be the same as in the original
design pattern. The only difference is that nobody ever has to worry about dangling pointers. (And,
of course, the newly available message type.)
So if your Subjects only send messages of one type, life is good, use any coding style that suits you.
But, if your Subjects send out different types of messages, and you expect you may change the
LifePolicy of your Observers, it's recommended that you use the helper template functions, because
they can infer the template policy parameter.
Nice. You can change the policy by only modifying the declaration. Otherwise, you'd have
to go and explicitly qualify every instance of the Subject's Attach, Notify and Detach methods with the different policy
in the definition file.
What else might Andrei and Martin do?
- Make the container type a policy of the Subject and LifePolicy. Perhaps the list should be sorted to facilitate frequent searching. Or perhaps it should be a map. Library usage could determine the instance of the container.
- Enabled the Empty Base Class optimization, by not making the policy a member of Observer.
- Different design: Not use friendship. (Used here [in the final code] because there's a unique symbiotic relationship between observer and observed.)
Let's head on over to the
finale.