Implementing the observer pattern in C

Discussion in 'C Programming' started by jacob navia, Mar 27, 2011.

  1. jacob navia

    jacob navia Guest

    An "observer" is an object that wants to be notified when something of
    interest happens in another object.

    Practically, within the context of the container library, an observer
    is a callback that is called when an operation for which the observer
    has subscribed happens.

    The observer interface looks like this:

    typedef void (*ObserverFunction)(void *ObservedObject,unsigned
    operation, void *ExtraInfo[]);

    typedef struct tagObserverInterface {

    int (*Subscribe)(void *ObservedObject, ObserverFunction callback,
    unsigned Operations);

    int (*Notify)(void *ObservedObject,unsigned operation,void
    *ExtraInfo1,void *ExtraInfo2);

    size_t (*Unsubscribe)(void *ObservedObject,ObserverFunction callback);
    } ObserverInterface;

    extern ObserverInterface iObserver;


    The process goes like this:

    1) You subscribe to a container requesting to be informed about a set
    of operations you want to observe.

    2) The container notifies you when one of the operations that you
    subscribed to happens.

    3) You unsubscribe when you are going out of scope or for whatever
    reason. Note that the container can unsubscribe too if it is going
    out of scope.

    Note that the notifications gives to the observer the maximum
    information possible so that there is no need to perform costly
    operations to retrieve it. Each operation (Add, Erase, Replace, etc)
    will pass a different set of extra information to the observer.

    The implementation is already at http://code.google.com/p/ccl. When
    an object has no observers, there is still a small overhead to pay:
    the test of a bit in the "Flags" field. I haven't been able to
    eliminate that "overhead", and that means that all operations that
    change the container have this test.

    When a message to an observer should be sent the overhead is bigger
    than that since the observer must search for all occurrences of the
    object in the observer table.

    The observer table associates objects with their callbacks. An object
    can have several callbacks and a callback can have several objects.

    At this stage I would like to have (if possible) feedback on the design
    of this. Am I missing something fundamental?


    Thanks in advance.

    jacob
     
    jacob navia, Mar 27, 2011
    #1
    1. Advertising

  2. jacob navia

    jacob navia Guest

    Le 28/03/11 00:25, christian.bau a écrit :
    > On Mar 27, 10:28 pm, jacob navia<> wrote:
    >
    >> At this stage I would like to have (if possible) feedback on the design
    >> of this. Am I missing something fundamental?

    >
    > You should have a look at Apple's CoreFoundation library.


    I had.

    <quote>
    A CFNotificationCenter object provides the means by which you can send a
    message, or notification, to any number of recipients, or observers,
    without having to know anything about the recipients.
    <end quote>

    Yes. The same thing in my interface.

    <quote>
    A notification message consists of a notification name (a CFString), a
    pointer value that identifies the object posting the notification, and
    an optional dictionary that contains additional information about the
    particular notification.
    <end quote>
    1) I send the object that sends the notification of course
    2) I do not use string names but bits to convey the same information
    3) I have the same extra information.

    <quote>
    To register as an observer of a notification, you call
    CFNotificationCenterAddObserver, providing an identifier for your
    observer, the callback function that should be called when the
    notification is posted, and the name of the notification and the object
    in which you are interested.
    <end quote>

    No identifier is needed since there is no observer object, just a
    callback. The callback is just its address. We are in C. Of course
    the callback could be part of another object/structure/whatever. This
    doesn't interest anyone.

    <quote>
    The observer identifier is passed back to the callback function, along
    with the notification information. You can use the identifier to
    distinguish multiple observers using the same callback function.
    <end quote>

    If you subscribe multiple times the same callback with different flags
    the callback will be called with different data. Since the name of the
    operation (as a bit of a set) is passed, the callback can know which
    operation actually occurred without any additional overhead. Note that
    my design has the same functionality with less overhead.

    <quote>
    The identifier is also used to unregister the observer with
    CFNotificationCenterRemoveObserver and
    CFNotificationCenterRemoveEveryObserver.
    <end quote>

    To unregister you call Unregister with:
    size_t (*Unsubscribe)(void *ObservedObject,ObserverFunction callback);

    If ObservedObject is NULL, all callbacks to the given callback function
    are destroyed.

    If callback is NULL, all observers that have the given objects are
    terminated.

    If both are non null, only the matching callbacks afre destroyed,
    regardeless of which operations they follow.

    I think that there is less overhead my implementation but I am of course
    biased :)
     
    jacob navia, Mar 28, 2011
    #2
    1. Advertising

  3. jacob navia

    jacob navia Guest

    I think it is easier to understand when you read the doc. Here is
    an excerpt of the essential parts of the doc:


    The observer interface
    ---------------------
    When a container changes its state, specifically when elements are added
    or removed, it is sometimes necessary to update relationships that can
    be very complex. The observer interface is designed to simplify this
    operation by allowing the container to emit notifications to other
    objects that have previously manifested interest in receiving them by
    subscribing to them.

    This interface then, establishes a relationship between two software
    entities:

    1. The container, that is responsible for sending the notifications when
    appropriate
    2. The receiver, that is an unspecified object represented by its
    callback function that is called when a change occurs that matches the
    notifications specified in the subscription.
    Since this relationship needs both objects, it will be finished when
    either object goes out of scope or breaks the relationship for whatever
    reason. Both objects can unsubscribe (terminate) their relationship.


    The interface
    -------------
    typedef void (*ObserverFunction)(void *ObservedObject, unsigned
    Operation, void *ExtraInfo[]);

    typedef struct tagObserverInterface { int (*Subscribe)(void
    *ObservedObject,ObserverFunction callback, unsigned Operations);

    int (*Notify)(void *ObservedObject,unsigned operation,void
    *ExtraInfo1,void *ExtraInfo2);


    size_t (*Unsubscribe)(void *ObservedObject,ObserverFunction callback);

    extern ObserverInterface iObserver;

    ObserverFunction

    typedef void (*ObserverFunction)(void *ObservedObject, unsigned
    Operation, void *ExtraInfo[]);

    Description:
    This function will be called by the interface when a notification is
    received for an observed object. The call happens after al arguments
    have been processed by the actual work of the function and they are in a
    consistent state. For the callbacks that are called when an object is
    deleted from a container the call happens before the call to free() and
    before any call to a destructor (if any) is done.

    Arguments:

    1. ObservedObject: Specifies the object that sends the notification,
    i.e. the container that has the subscription. It is assumed that this
    container conforms to the iGeneric interface.

    2. Operation: The operation that provoked the notification. Since it is
    possible to subscribe to several operations with only one callback
    function, this argument allows the callback to discriminate between the
    operation notifications.

    3. ExtraInfo: This argument is specific to each operation and conveys
    further infor- mation1 for each operation.

    None of the arguments will be ever NULL or zero.


    Subscribe

    int (*Subscribe)(void *ObservedObject, ObserverFunction callback,
    unsigned Operations);

    Description:
    This function establishes the relationship between the observed object
    (argument 1) and the observer, represented by its callback (argument 2).
    The third argument establishes which operations are to be observed. This
    operation performs an allocation to register the relationship in the
    observer interface tables, therefore it can fail with an out of memory
    condition.

    Notify

    int (*Notify)(void *ObservedObject,unsigned Operation, void
    *ExtraInfo1,void *ExtraInfo2);

    Description:
    This function will be used by the container to send a message to the
    receiver callback. The arguments correspond roughly to the arguments the
    callback func- tion will receive. This function will call all the
    objects that are observing ObservedObject and that have subscribed to
    the operation specified in the Operation argument. This implies a search
    through the observer interface table, and possibly several calls, making
    this function quite expensive. The time needed is roughly proportional
    to the number of registered callbacks and the complexity of the
    callbacks themselves.


    Unsubscribe
    size_t (*Unsubscribe)(void *ObservedObject, ObserverFunction callback);

    Description:
    This function breaks the relationship between the observed object and
    the observer. There are several combinations of both arguments:
    • The ObservedObject argument is NULL . This means that the callback
    object wants to break its relationship to all objects it is observing.
    The observer interface will remove all relationships that contain this
    callback from its tables.
    • The callback argument is NULL . This means that the given
    ObservedObject is going out of scope and wants to break all
    relationships to all its observers. The interface removes from its
    tables all relationships that have this object as the observed object.
    This happens normally immediately after the notification FINALIZE is sent.
    • If both callback and ObservedObject are non NULL , only the matching
    relationships will be removed from the tables.
     
    jacob navia, Mar 29, 2011
    #3
  4. jacob navia

    jacob navia Guest

    Le 28/03/11 17:41, Scott Fluhrer a écrit :

    > In my experience, when a routine registers a callback, it's often useful if
    > that routine can specify a 'context' parameter so that when the callback
    > occurs, the callback routine has some, well, context.
    >


    Yes, I thought about that, and I do send a lot of context.

    The callback receives
    (1) The address of the concerned container
    (2) The operation code
    (3) Up to two extra arguments further furnishing the new or deleted
    object from the container and its index.

    With this context I think that the callback has all elements to do its
    job, whatever that may be. But maybe I forgot something.

    > One example that might apply to this specific interface might be if we're
    > observing data structure A so that we can keep data structure B sync'ed to
    > it; it might be useful if the callback has a pointer to B (especially if
    > there are several different A's we're observing at the same time).
    >



    Ahh... right, now I see your point. I did not think about that.

    I have to try to keep the thing simple. If I implement it, it would be
    another argument to Subscribe and another argument to the callback...

    Will think about that.

    > This can be done by adding a (void *) parameter to the Subscribe routine,
    > which is passed to the ObserverFunction. It might also be wise to have the
    > caller pass it to the Unsubscribe routine (so that if two different
    > observers registered to the same object, that's another way to specify which
    > is shutting down; a tad obscure, but it might be useful).


    There is an explicit "Unsubscribe" routine.
     
    jacob navia, Mar 29, 2011
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Beatrice Rutger
    Replies:
    0
    Views:
    759
    Beatrice Rutger
    Jun 5, 2005
  2. Replies:
    6
    Views:
    753
    Chris Uppal
    Feb 13, 2006
  3. mem

    Observer pattern

    mem, May 18, 2004, in forum: C++
    Replies:
    2
    Views:
    554
    adiian
    Jun 4, 2007
  4. jacob navia

    Implementing an observer

    jacob navia, Nov 18, 2010, in forum: C Programming
    Replies:
    24
    Views:
    773
    Malcolm McLean
    Nov 25, 2010
  5. Adam Akhtar
    Replies:
    8
    Views:
    229
    Adam Akhtar
    Nov 11, 2008
Loading...

Share This Page