The Observer Pattern - further considerations and generalised C++ implementation

前端 未结 2 2045

A C++ MVC framework I’m writing makes heavy use of the observer pattern. I have had a thorough read of the related chapter in Design Patterns (GoF, 1995) and had a look at a mul

相关标签:
2条回答
  • 2021-01-30 15:44

    (start of part II)

    Subjects

    The Subscription Process

    What is being stored?

    Depending on the specific implementation, subjects may store the following data when observers subscribe:

    • Event Id – The interest, or what event the observer subscribes to.
    • The Observer Instance – most commonly in the form of an object pointer.
    • A member function pointer – if using an arbitrary handler.

    This data will form the parameters of the subscribe method:

    // Subscription with an overridden handler (where the observer class has a base class handler method).
    aSubject->Subscribe( "SizeChanged", this );
    
    // Subscription with an arbitrary handler.
    aSubject->Subscribe( "SizeChanged", this, &ThisObserverClass::OnSizeChanged );
    

    It is worth noting that if arbitrary handler are used, member function pointers are likely to be packed together with the observer instance in a class or a struct to form a delegate. And so the Subscribe() method might have the following signature:

    // Delegate = object pointer + member function pointer.
    void Subject::Subscribe( EventId aEventId, Delegate aDelegate )
    {
       //...
    }
    

    The actual storing (possibly within a std::map) will involve the event id as the key and the delegate as the value.

    Implementing Event IDs

    Defining event IDs outside the subject class that fires them could simplify the access to these IDs. But generally speaking, the events fired by a subject are unique to that subject. Thus, in most cases it will be logical to declare the event IDs within the subject class.

    Although there are more than a few ways to declare event IDs, only 3 that are of most interest are discussed here:

    Enums seem, on the face of it, the most logical choice:

    class FigureSubject : public Subject
    {
    public:
        enum {
            evSizeChanged,
            evPositionChanged
        };
    };
    

    The comparison of enums (which will happen upon subscription and firing) is quick. Perhaps the only inconvenience with this strategy is that observers need to specify the class upon subscription:

    // 'FigureSubject::' is the annoying bit.
    aSubject->Subscribe( FigureSubject::evSizeChanged, this );
    

    Strings provide a 'looser' option to enums, as typically the subject class will not declare them like enums; instead, clients will just use:

    // Observer code
    aFigure->Subscribe( "evSizeChanged", this );
    

    The nice thing about strings is that most compilers colour code them differently from other parameters, which somehow improves the readability of the code:

    // Within a concrete subject
    Fire( "evSizeChanged", mSize, iOldSize );
    

    But the issue with strings is that we cannot tell at runtime if we have misspelled an event name. Also, string comparison takes longer than enum comparison, as strings have to be compared character by character.

    Types is the last option discussed here:

    class FigureSubject : public Subject
    {
    public:
        // Declaring the events this subject supports.
        class SizeChangedEventType     : public Event {} SizeChangedEvent;
        class PositionChangedEventType : public Event {} PositionChangedEvent;
    };
    

    The benefit of using types is that they allow the overloading of methods like Subscribe() (which we'll soon see can solve a common problem with observers):

    // This particular method will be called only if the event type is SizeChangedType
    FigureSubject::Subscribe( SizeChangedType aEvent, void *aObserver )
    {
        Subject::Subscribe( aEvent, aObserver );
    
        Fire( aEvent, GetSize(), aObserver );
    }
    

    But again, observers need a bit of extra code to subscribe:

    // Observer code
    aFigure->Subscribe( aFigure->SizeChangedEvent, this );
    

    Where to Store Observers?

    Implementation point 1 in Design Pattern deals with where should the observers of each subject be stored. This section adds to that discussion, providing 3 options:

    • Global Hash
    • Per Subject
    • Per Event

    As suggested in Design Patterns, one place to store the subject-observer map is in a global hash table. The table will include the subject, the event and the observer (or delegate). Of all methods, this one is the most memory efficient as subjects don’t consume a member variable to store the list of observers in – there is only one global list. This could be useful if the pattern is implemented in javascript frameworks due to the limited memory offered by browsers. The main disadvantage of this method is that it is also the slowest – for every event being fired, we first have to filter the requested subject from the global hash, then filter the requested event, and only then iterate through all observers.

    Also suggested in Design Patterns is that every subject keeps a list of its observers. This will consume slightly more memory (in the form of an std::map member variable per subject), but it provides better performance than a global hash as the subject only need to filter the requested event, then iterate through all the observers of this event. The code may look like so:

    class Subject
    {
    protected:    
        // A callback is represented by the event id and the delegate.
        typedef std::pair< EventId, Delegate > Callback;
    
        // A map type to store callbacks
        typedef std::multimap< EventId, Delegate > Callbacks;
    
        // A callbacks iterator
        typedef Callbacks::iterator CallbackIterator;
    
        // A range of iterators for use when retrieving the range of callbacks
        // of a specific event.
        typedef std::pair< CallbackIterator, CallbackIterator> CallbacksRange;
    
        // The actual callback list
        Callbacks mCallbacks;
    public:
        void Fire( EventId aEventId )
        {
            CallbacksRange   iEventCallbacks;
            CallbackIterator iIterator;
    
            // Get the callbacks for the request event.
            iEventCallbacks = mCallbacks.equal_range( aEventId );
    
            for ( iIterator = iEventCallbacks.first; iIterator != iEventCallbacks.second; ++iIterator  )
            {
                // Do the firing.
            }
        }
    };
    

    Not suggested in Design Patterns is the option of having each event as a member variable, then store observers within the event itself. This is the most memory consuming strategy as not only each event consumes a member variable, but there’s also an std::vector storing observers per event. However, this strategy provides best performance as there’s no filtering to be done and we can just iterate through the attached observers. This strategy will also involve the most simplistic code compared to the other two. To implement it, an event will have to offer subscription and fire methods:

    class Event
    {
    public:
        void Subscribe( void *aDelegate );
        void Unsubscribe( void *aDelegate );
    
        void Fire();
    };
    

    The subject may look something like so:

    class ConcreteSubject : public Subject
    {
    public:
        // Declaring the events this subject supports.
        class SizeChangedEventType     : public Event {} SizeChangedEvent;
        class PositionChangedEventType : public Event {} PositionChangedEvent;
    };
    

    Although observers could theoretically subscribe to the events directly, we’ll see that it pays to go through the subject instead:

    // Subscribing to the event directly - possible but will limit features.
    aSubject->SizeChangedEvent.Subscribe( this );
    
    // Subscribing via the subject.
    aSubject->Subscribe( aSubject->SizeChangedEvent, this );
    

    The 3 strategies provide a clear case of the store-vs-compute tradeoff. And can be compared using the following table:

    enter image description here

    The approach taken should account for the following:

    • The subjects/observers ratio – The memory penalty will be higher in systems with few observers and many subjects, especially where the typical subject will have no or just one observer.
    • The frequency of notifications – Where the more frequent notifications are, the higher the performance penalty will be.

    When the observer pattern is used to notify MouseMove events, one may want to consider more the performance of the implementation. As far as memory penalties go, the following calculation may help. Given:

    • Using the per-event strategy
    • A typical 64-bit system
    • Each subject has an average of 8 events

    8 million subject instances will consume just below 1GB of RAM (events memory only).

    Same observer, same event?

    One key question in the implementation of the observer pattern is whether or not we allow the same observer to subscribe more than once to the same event (of the same subject).

    To begin with, if we do allow it we are likely to use std::multimap instead of std::map. In addition, the following line will be problematic:

    aSubject->Unsubscribe( evSizeChanged, this );
    

    Since there is no way for the subject to know which of the previous subscriptions (there can be more than one!) to unsubscribe from. So Subscribe() will have to return a token that Unsubscribe() will use, and the whole implementation gets far more complex.

    On the face of it, it seems rather idiotic – why would the same object like to subscribe to the same event more than once? But consider the following code:

    class Figure
    {
    public:
        Figure( Subject *aSubject )
        {
            // We subscribe to the subject on size events
            aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );
        }
    
        void OnSizeChanged( Size aSize )
        {
        }
    };
    
    class Circle : public Figure
    {
    public:
        Circle( Subject *aSubject )
          : Figure( aSubject) 
        {
            // We subscribe to the subject on size events
            aSubject->Subscribe( evSizeChanged, this, &Circle::OnSizeChanged );
        }
    
        void OnSizeChanged( Size aSize )
        {
        }
    };
    

    This particular code will lead to the same object subscribing to the same event twice. It is also worth noticing that since the OnSizeChanged() method is not virtual, the member function pointer will be different between the two subscription calls. So in this particular case the subject could also compare the member function pointer, and the unsubscribe signature will be:

    aSubject->Unsubscribe( evSizeChanged, this, &Circle::OnSizeChanged );
    

    But if the OnSizeChanged() is virtual, there is no way to distinguish between the two subscription calls without a token.

    Truth to be told, if OnSizeChanged() is virtual, there is no reason for the Circle class to subscribe to the event again since it is its own handler that will be called and not that of the base class:

    class Figure
    {
    public:
        // Constructor
        Figure( Subject *aSubject )
        {
            // We subscribe to the subject on size events
            aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );
        }
    
        virtual void OnSizeChanged( Size aSize )
        {
        }
    };
    
    class Circle : public Figure
    {
    public:
        // Constructor
        Circle( Subject *aSubject )
          : Figure( aSubject) { }
    
        // This handler will be called first when evSizeChanged is fired.
        virtual void OnSizeChanged( Size aSize )
        {
            // And we can call the base class handler if we want.
            Figure::OnSizeChanged( aSize );
        }
    };
    

    This code probably represents the best compromise when it comes to both the base class and its subclass having to respond to the same event. But it requires the handlers to be virtual and the programmer to know which events the base class subscribes to.

    Disallowing the same observer to subscribe more than once to the same event greatly simplifies the implementation of the pattern. It saves the need to compare member function pointers (a tricky business) and it allows Unsubscribe() to be as short as this (even if an MFP was provided with Subscribe()):

    aSubject->Unsubscribe( evSizeChanged, this );
    

    Post Subscription Consistency

    One of the prime aims of the observer pattern is to keep observers consistent with their subject state – and we have already seen that state change events do exactly that.

    It is somewhat surprising that it went amiss to the authors of Design Patterns to assert that when an observer subscribes to a subject, the state of the former is not yet consistent with the state of the latter. Consider this code:

    class Figure
    {
    public:
        // Constructor
        Figure( FigureSubject *aSubject )
        {
            // We subscribe to the subject on size events
            aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );
        }
    
        virtual void OnSizeChanged( Size aSize )
        {
            mSize = aSize;
    
            // Refresh the view.
            Refresh();
        }
    private:
        Size mSize;
    };
    

    Upon creation, the Figure class does subscribe with its subject, but its size is not consistent with that of the subject, nor it will refresh the view to display what should be its correct size.

    When the observer pattern is used to fire state change event, one will often find the need to update the observers manually after subscription. One way to achieve this is within the observer:

    class Figure
    {
    public:
        Figure( FigureSubject *aSubject )
        {
            // We subscribe to the subject on size events
            aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );
    
            // Now make sure we're consistent with the subject.
            OnSizeChanged( aSubject->GetSize() );
        }
    
        // ...
    };
    

    But imagine a subject with 12 state change events. It would be nice if the whole thing would happen automatically, where upon subscription the subject will fire the correct event back to the observer.

    One way to achieve this requires an overloaded Subscribe() method in the concrete subject:

    // This method assumes that each event has its own unique class, so the method
    // can be overloaded.
    FigureSubject::Subscribe( evSizeChanged aEvent, Delegate aDelegate )
    {
        Subject::Subscribe( aEvent, aDelegate );
    
        // Notice the last argument in this call.
        Fire( aEvent, GetSize(), aDelegate );
    }
    

    Then the observer code:

    class Figure
    {
    public:
        Figure( FigureSubject *aSubject )
        {
            // We subscribe to the subject on size events.
            // The subject will fire the event upon subscription
            aSubject->Subscribe( evSizeChanged, MAKEDELEGATE( this, &Figure::OnSizeChanged ) );
        }
    
        // ...
    };
    

    Notice that the Fire call now takes an extra parameter (aDelegate) so it can only update that specific observer and not observers already subscribed.

    gxObserver deals with this scenario by defining bound events. These are events whose only parameter (other than an optional sender) is bound to a getter or a member variable:

    class Subject : virtual public gxSubject
    {
    public:
        gxDefineBoundEvent( evAge, int, GetAge() )
    
        int GetAge() { return mAge; }
    private:
        int mAge;    
    }
    

    Which also allows subjects to fire an event providing only the event type:

    // Same as Fire( evAge, GetAge() );
    Fire( evAge );
    

    Regardless of the mechanism used, it is worth remembering:

    • There need to be a way to ensure observers are consistent with their subject right after state event subscription.
    • It is better if this is implemented in the subject class, not in the observer code.
    • The Fire() method may need an extra optional parameter so it can fire to a single observer (the one that just subscribed).

    The Firing Process

    Fire From the Base Class

    The following code snippet shows the implementation of event firing in JUCE:

    void Button::sendClickMessage (const ModifierKeys& modifiers)
    {
        for (int i = buttonListeners.size(); --i >= 0;)
        {
            ButtonListener* const bl = (ButtonListener*) buttonListeners[i];
            bl->buttonClicked (this);
        }
    }
    

    There are a few issues with this approach:

    • It is apparent from the code that the class maintains its own list of buttonListeners, which would imply it also has its own AddListener and RemoveListener methods.
    • The concrete subject is the one looping through the list of observers.
    • The subject is highly coupled to its observer, as it knows both its class (ButtonListener) and the actual callback method within it (buttonClicked).

    All these points mean that there is no base subject class. If taking this approach, any firing/subscription mechanism will have to be re-implemented per each concrete subject. This is counter object oriented programming.

    It would be sensible to have the observers’ management, their traversal, and the actual notification done in a subject base class; this way, any changes to the underlining mechanism (introducing thread-safety, for instance) would not require a change in each concrete subject. This will leave our concrete subjects with a well-encapsulated and simple interface, and firing is reduced to one line:

    // In a concreate subject
    Fire( evSize, GetSize() );
    

    Events Suspension and Resumption

    Many applications and frameworks will find the need to suspend the firing of events for a specific subject. Sometimes we’d like the suspended events to queue up and be fired when we resume firing, sometimes we just want to ignore them. As far as the subject interface goes:

    class Subject
    {
    public:
        void SuspendEvents( bool aQueueSuspended );
        void ResumeEvents();
    };
    

    One example to where event suspension is useful is during the destruction of composite objects. When a composite object is being destroyed, it first destroys all of its children, which first destroy all of their children, and so on. Now if these composite objects reside in the model layer, they’ll need to notify their corresponding objects in the view layer (say using an evBeforeDestroy event):

    enter image description here

    Now in this specific case there is no need for each object to fire an evBeforeDestroy event – it will do if only the top-level model object will (deleting the top-level view object will also delete all its children). So whenever a composite as such is destroyed, it would like to suspend the events of its children (without queuing them).

    Another example would be the loading of document involving many objects, some observe others. While a subject may load first and have its size set based on the file data, its observers may not yet be loaded and thus would not get the size change notification. In this case, we’d like to suspend events before load, but queue them until the document was loaded in full. Firing all queued events then will ensure all observers are consistent with their subjects.

    Lastly, an optimised queue will not queue the same event for the same subject more than once. When notifications resume, there is no point notifying observers of a size change to (10,10) if a later queued event will notify (20,20). So the most recent version of each event is the one the queue should keep.

    How to add subject capabilities to a class?

    A typical subject interface would look something along these lines:

    class Subject
    {
    public:
        virtual void Subscribe( aEventId, aDelegate );
        virtual void Unsubscribe( aEventId, aDelegate );
        virtual void Fire( aEventId );
    }
    

    The question is how do we add this interface to various classes. There are three options to consider:

    • Inheritance
    • Composition
    • Multiple Inheritance

    Inheritance

    In Design Patterns, a ConcreteSubject class inherits from a Subject class.

    class ScrollManager: public Subject
    {
    }
    

    Both the class diagrams and sample code in Design Pattern could easily leave one thinking that this is how to go about it. But the very same book warns against inheritance and recommends favouring composition over it. This is sensible: consider an application with many composites where only some are subjects; shall the Composite class inherit from the Subject class? If so, many composites will have subject capabilities they don’t need, and there may a memory penalty in the form of an observer list variable that is always empty.

    Composition

    Most applications and frameworks will find the need to ‘plug’ the subject capabilities only into selected classes, which are not necessarily base classes. Composition allows exactly that. In practice, a class will have a member mSubject providing interface to all the subject methods, like so:

    class ScrollManager: public SomeObject
    {
    public:
        Subject mSubject;
    }
    

    One issue with this strategy is that it carries memory penalty (a member variable) for each subject-supported class. The other is that it makes accessing the subject protocol somewhat cumbersome:

    // Notification within a class composed with the subject protocol.
    mSubject.Fire( ... );
    
    // Or the registration from an observer.
    aScrollManager.mSubject.Subscribe( ... );
    

    Multiple Inheritance

    Multiple Inheritance allows us to compose the subject protocol into a class at will, but without the pitfalls of member composition:

    class ScrollManager: public SomeObject,
                         public virtual Subject
    {
    }
    

    This way, we are getting rid of mSubject from the previous example, so we’re left with:

    // Notification within a subject class.
    Fire( ... );
    
    // Or the registration from an observer.
    aScrollManager.Subscribe( ... );
    

    Notice that we use public virtual for the subject inheritance, so if subclasses of ScrollManager decide to re-inherit the protocol, we don’t get the interface twice. But it is fair to assume that programmers will notice that a base class is already a subject, thus there’s no reason to re-inherit it.

    While multiple inheritance is generally not encouraged and not all languages support it, it is well worth considering for this purpose. ExtJs, which being based on Javascript does not support multiple inheritance, uses mixins to achieve the same thing:

    Ext.define('Employee', {
        mixins: {
            observable: 'Ext.util.Observable'
        },
    
        constructor: function (config) {
            this.mixins.observable.constructor.call(this, config);
        }
    });
    

    Conclusion

    To conclude this article, generalised implementations of the observer pattern should account to these key points:

    • Subjects fire both state and stateless events – the latter can only be realised with a push model.
    • Subjects typically fire more than one type of event.
    • Observers may subscribe to the same event with a multitude of subjects. Meaning the subject-observer protocol should allow the sender to be spelt.
    • Arbitrary event handlers greatly simplify client code and facilitate the preferred vari-arity push model; but their implementation is not straight forward and will result in a more complex subject code.
    • Event handlers may need to be virtual.
    • Observers can be stored in a global hash, per subject, or per event. The choice forms a tradeoff between memory, performance and code simplicity.
    • Ideally an observer only subscribes once with the same subject to the same event.
    • Keeping observers consistent with their subject’s state straight after subscription is something to keep in mind.
    • The firing of events may need to be suspended with a queuing option, and then resumed.
    • Multiple inheritance is worth considering when adding subject capabilities to a class.

    (end of part II)

    0 讨论(0)
  • 2021-01-30 16:05

    enter image description here

    (start of part I)

    Prerequisites

    It’s Not All About State

    Design Patterns ties the observer pattern to an object 'state'. As seen in the class diagram above (from Design Patterns), a subject’s state can be set using the SetState() method; upon state change the subject will notify all of its observers; then observers can inquire the new state using the GetState() method.

    However, GetState() is not an actual method in subject base class. Instead, each concrete subject provides its own specialised state methods. An actual code might look like this:

    SomeObserver::onUpdate( aScrollManager )
    {
        // GetScrollPosition() is a specialised GetState();
        aScrollPosition = aScrollManager->GetScrollPosition();
    }
    

    What’s an object state? We define it as the collection of state variables – member variables that need to be persisted (for later reinstatement). For instance, both BorderWidth and FillColour could be state variables of a Figure class.

    The idea that we can have more than one state variable – and thus an object’s state can change in more than one way – is important. It means that subjects are likely to fire more than one type of state change event. It also explains why it makes little sense to have a GetState() method in the subject base class.

    But an observer pattern that can only handle state changes is an incomplete one – it is common for observers to observe stateless notifications, i.e., ones that are not state related. For example, the KeyPress or MouseMove OS events; or events like BeforeChildRemove, which clearly does not denote an actual state change. These stateless events are enough to justify a push mechanism – if observers cannot retrieve the change information from the subject, all the information will have to be served with the notification (more on this shortly).

    There Will Be Many Events

    It is easy to see how in 'real life' a subject may fire many types of events; a quick look at the ExtJs library will reveal that some classes offer upwards of 30 events. Thus, a generalised subject-observer protocol has to integrate what Design Patterns calls an 'interest' – allowing observers to subscribe to a particular event, and subjects to fire that event only to interested observers.

    // A subscription with no interest.
    aScrollManager->Subscribe( this );
    
    // A subscription with an interest.
    aScrollManager->Subscribe( this, "ScrollPositionChange" );
    

    It Could Be Many-to-many

    A single observer may observe the same event from a multitude of subjects (making the observer-subject relationship many-to-many). A property inspector, for instance, may listen to a change in the same property of many selected objects. If observers are interested in which subject sent the notification, the notification will have to incorporate the sender:

    SomeSubject::AdjustBounds( aNewBounds )
    {
        ...
        // The subject also sends a pointer to itself.
        Fire( "BoundsChanged", this, aNewBounds );
    }
    
    // And the observer receives it.
    SomeObserver::OnBoundsChanged( aSender, aNewBounds )
    {
    }
    

    It is worth noting, however, that in many cases observers don't care about the sender identity. For instance, when the subject is a singleton or when the observer’s handling of the event is not subject-dependant. So instead of forcing the sender to be part of a protocol we should allow it to be, leaving it to the programmer whether or not to spell the sender.

    Observers

    Event Handlers

    The observer’s method which handles events (ie, the event handler) can come in two forms: overridden or arbitrary. Providing a critical and complex part in the implementation of the observers, the two are discussed in this section.

    Overridden Handler

    An overridden handler is the solution presented by Design Patterns. The base Subject class defines a virtual OnEvent() method, and subclasses override it:

    class Observer
    {
    public:
        virtual void OnEvent( EventType aEventType, Subject* aSubject ) = 0;
    };
    
    class ConcreteObserver
    {
        virtual void OnEvent( EventType aEventType, Subject* aSubject )
        {
        }
    };
    

    Note that we have already accounted for the idea that subjects typically fire more than one type of event. But handling all events (particularly if there are tens of them) in the OnEvent method is unwieldy - we can write better code if each event is handled in its own handler; effectively, this makes OnEvent an event router to other handlers:

    void ConcreteObserver::OnEvent( EventType aEventType, Subject* aSubject )
    {
        switch( aEventType )
        {
            case evSizeChanged:
                OnSizeChanged( aSubject );
                break;
            case evPositionChanged:
                OnPositionChanged( aSubject );
                break;
        }
    }
    
    void ConcreteObserver::OnSizeChanged( Subject* aSubject )
    {
    }
    
    void ConcreteObserver::OnPositionChanged( Subject* aSubject )
    {
    }
    

    The advantage in having an overridden (base class) handler is that it is dead easy to implement. An observer subscribing to a subject can do so by providing a reference to itself:

    void ConcreteObserver::Hook()
    {
        aSubject->Subscribe( evSizeChanged, this );
    }
    

    Then the subject just keeps a list of Observer objects and the firing code might look like so:

    void Subject::Fire( aEventType )
    {
        for ( /* each observer as aObserver */)
        {
            aObserver->OnEvent( aEventType, this );
        }
    }
    

    The disadvantage of overridden handler is that its signature is fixed, which makes the passing of extra parameters (in a push model) tricky. In addition, for each event the programmer has to maintain two bits of code: the router (OnEvent) and the actual handler (OnSizeChanged).

    Arbitrary Handlers

    The first step in overcoming the shortfalls of an overridden OnEvent handler is… by not having it all! It would be nice if we could tell the subject which method is to handle each event. Something like so:

    void SomeClass::Hook()
    {
        // A readable Subscribe( evSizeChanged, OnSizeChanged ) has to be written like this:
        aSubject->Subscribe( evSizeChanged, this, &ConcreteObserver::OnSizeChanged );
    }
    
    void SomeClass::OnSizeChanged( Subject* aSubject )
    {
    }
    

    Notice that with this implementation we no longer need our class to inherit from the Observer class; in fact, we don’t need an Observer class at all. This idea is not a new one, it was described in length in Herb Sutter’s 2003 Dr Dobbs article called ‘Generalizing Observer’. But, the implementation of arbitrary callbacks in C++ is not a straightforward afair. Herb was using the function facility in his article, but unfortunately a key issue in his proposal was not fully resolved. The issue, and its solution are described below.

    Since C++ does not provide native delegates, we need to use member function pointers (MFP). MFPs in C++ are class function pointers and not object function pointers, thus we had to provide the Subscribe method with both &ConcreteObserver::OnSizeChanged (The MFP) and this (the object instance). We will call this combination a delegate.

    Member Function Pointer + Object Instance = Delegate

    The implementation of the Subject class may rely on the ability to compare delegates. For instance, in cases we wish to fire an event to a specific delegate, or when we want to unsubscribe a specific delegate. If the handler is not a virtual one and belongs to the class subscribing (as opposed to a handler declared in a base class), delegates are likely to be comparable. But in most other cases the compiler or the complexity of the inheritance tree (virtual or multiple inheritance) will render them incomparable. Don Clugston has written a fantastic in-depth article on this problem, in which he also provides a C++ library that overcomes the problem; while not standard compliant, the library works with pretty much every compiler out there.

    It is worth asking whether virtual event handlers are something we really need; that is, whether we may have a scenario where an observer subclass would like to override (or extend) the event handling behaviour of its (concrete observer) base class. Sadly, the answer is that this is a well possible. So a generalised observer implementation should allow virtual handlers, and we shall soon see an example of this.

    The Update Protocol

    Design Patterns’ implementation point 7 describes the pull vs push models. This section extends the discussion.

    Pull

    With the pull model, the subject sends minimal notification data and then the observer is required to retrieve further information from the subject.

    We have already established that the pull model won’t work for stateless events such as BeforeChildRemove. It is perhaps also worth mentioning that with the pull model the programmer is required to add lines of code to each event handler that would not exist with the push model:

    // Pull model
    void SomeClass::OnSizeChanged( Subject* aSubject )
    {
        // Annoying - I wish I didn't had to write this line.
        Size iSize = aSubject->GetSize();
    }
    
    // Push model
    void SomeClass::OnSizeChanged( Subject* aSubject, Size aSize )
    {
        // Nice! We already have the size.
    }
    

    Another thing worth remembering is that we can implement the pull model using a push model but not the other way around. Although the push model serves the observer with all the information it needs, a programmer may wish to send no information with specific events, and have the observers enquiring the subject for more information.

    Fixed-arity Push

    With a fixed-arity push model, the information a notification carries is delivered to the handler via an agreed amount and type of parameters. This is very easy to implement, but as different events will have a different amount of parameters, some workaround has to be found. The only workaround in this case would be to pack the event information into a structure (or a class) that is then delivered to the handler:

    // The event base class
    struct evEvent
    {
    };
    
    // A concrete event
    struct evSizeChanged : public evEvent
    {
        // A constructor with all parameters specified.
        evSizeChanged( Figure *aSender, Size &aSize )
          : mSender( aSender ), mSize( aSize ) {}
    
        // A shorter constructor with only sender specified.
        evSizeChanged( Figure *aSender )
          : mSender( aSender )
        {
            mSize = aSender->GetSize();
        }
    
        Figure *mSender;
        Size    mSize;
    };
    
    // The observer's event handler, it uses the event base class.
    void SomeObserver::OnSizeChanged( evEvent *aEvent )
    {
        // We need to cast the event parameter to our derived event type.
        evSizeChanged *iEvent = static_cast<evSizeChanged*>(aEvent);
    
        // Now we can get the size.
        Size iSize  = iEvent->mSize;
    }
    

    Now although the protocol between the subject and its observers is simple, the actual implementation is rather lengthy. There are a few disadvantages to consider:

    First, we need to write quite a lot of code (see evSizeChanged) for each event. A lot of code is bad.

    Second, there are some design questions involved that are not easy to answer: shall we declare evSizeChanged alongside the Size class, or alongside the subject that fires it? If you think about it, neither is ideal. Then, will a size change notification always carry the same parameters, or would it be subject-dependent? (Answer: the latter is possible.)

    Third, someone will need to create an instance of the event before firing, and delete it after. So either the subject code will look like this:

    // Argh! 3 lines of code to fire an event.
    evSizeChanged *iEvent = new evSizeChanged( this );
    Fire( iEvent );
    delete iEvent;
    

    Or we do this:

    // If you are a programmer looking at this line than just relax!
    // Although you can't see it, the Fire method will delete this 
    // event when it exits, so no memory leak!
    // Yes, yes... I know, it's a bad programming practice, but it works.
    // Oh.. and I'm not going to put such comment on every call to Fire(),
    // I just hope this is the first Fire() you'll look at and just 
    // remember.
    Fire( new evSizeChanged( this ) );
    

    Forth, there’s a casting business going on. We have done the casting within the handler, but it is also possible to do it within the subject’s Fire() method. But this will either involve dynamic casting (performance costly), or we do a static cast which could result in a catastrophe if the event being fired and the one the handler expects do not match.

    Fifth, the handler arity is little readable:

    // What's in aEvent? A programmer will have to look at the event class 
    // itself to work this one out.
    void SomeObserver::OnSizeChanged( evSizeChanged *aEvent )
    {
    }
    

    As opposed to this:

    void SomeObserver::OnSizeChanged( ZoomManager* aManager, Size aSize )
    {
    }
    

    Which leads us to the next section.

    Vari-arity Push

    As far as looking at code goes, many programmers would like to see this subject code:

    void Figure::AdjustBounds( Size &aSize )
    {
         // Do something here.
    
         // Now fire
         Fire( evSizeChanged, this, aSize );
    }
    
    void Figure::Hide()
    {
         // Do something here.
    
         // Now fire
         Fire( evVisibilityChanged, false );
    }
    

    And this observer code:

    void SomeObserver::OnSizeChanged( Figure* aFigure, Size aSize )
    {
    }
    
    void SomeObserver::OnVisibilityChanged( aIsVisible )
    {
    }
    

    The subject’s Fire() methods and the observer handlers have different arity per event. The code is readable and as short as we could have hoped for.

    This implementation involves a very clean client code, but would bring about a rather complex Subject code (with a multitude of function templates and possibly other goodies). This is a trade-off most programmers will take – it is better to have complex code in one place (the Subject class), than in many (the client code); and given that the subject class works immaculately, a programmer might just regard it as a black-box, caring little about how it is implemented.

    What is worth considering is how and when to ensure that the Fire arity and the handler arity match. We could do it in run-time, and if the two don’t match we raise an assertion. But it would be really nice if we get an error during compile time, for which to work we’ll have to declare the arity of each event explicitly, something like so:

    class Figure : public Composite, 
                   public virtual Subject
    {
    public:
        // The DeclareEvent macro will store the arity somehow, which will
        // then be used by Subscribe() and Fire() to ensure arity match 
        // during compile time.
        DeclareEvent( evSizeChanged, Figure*, Size )
        DeclareEvent( evVisibilityChanged, bool )
    };
    

    We’ll see later how these event declaration have another important role.

    (end of part I)

    0 讨论(0)
提交回复
热议问题