Observing a container

Discussion in 'C Programming' started by jacob navia, Dec 10, 2013.

  1. jacob navia

    jacob navia Guest

    The observer interface
    In its general form, the observer design pattern can be defined as a
    one-to-many dependency between objects so that when one object changes
    state, all its dependents are notified and updated automatically.
    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. In general notifications are sent only when one of
    the defined operations for a container occur, mostly operations that
    change the number of elements.
    This interface then, establishes a relationship between two software
    1. The container, that is responsible for sending the notifications when
    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.
    • It is in general a bad idea to modify the object being observed during
    a notification since this could trigger other notification messages.
    Implementations are not required to avoid this situation that is the
    responsibility of the programmer. Contrary to the iterator interface no
    error is issued when a possible infinite loop is started.
    Implementations may catch the error by limiting the number of recursive
    invocations of this interface but they are not required to do so.
    Since all messages sent by the containers have different type of
    information in the same two arguments that each message is associated
    with, there is no possible compile time control of the usage of the
    received pointers or numbers. The observer function must correctly
    discriminate between the different messages it can receive.
    An alternative design would have been to specify not one type of
    observer function but to define a different functio n type for each
    possible message the containers could send. We would have then a
    SubscribeAdd SubscribeErase SubscribeReplace functions, combined with
    NotifyAdd, NotifyErase, NotifyReplace functions. That design would have
    been easier to control at compile time. It was rejected because of the
    increased complexity of the interface and the necessity for the user to
    define a lot of functions just to know when something as simple as ”Was
    this container modified?” happened
    The interface
    typedef void (*ObserverFunction)(const void *ObservedObject,
    unsigned Operation,
    void *ExtraInfo[]);
    typedef struct tagObserverInterface {
    int (*Subscribe)(void *ObservedObject,
    ObserverFunction callback, unsigned
    int (*Notify)(const void *ObservedObject,unsigned operation,
    void *ExtraInfo1,void *ExtraInfo2);
    size_t (*Unsubscribe)(void *ObservedObject,
    ObserverFunction callback);
    } ObserverInterface;
    extern ObserverInterface iObserver;
    typedef void (*ObserverFunction)(void *ObservedObject,
    unsigned Operation, void *ExtraInfo[]);
    This function will be called by the interface when a notification is
    received for an observed object. The call happens after all arguments
    have been processed, the actual work of the function is finished (when
    adding an object) or not yet done (when destroying an object). The
    container is in a consistent state. For the callbacks that are
    called when an object is deleted from a container the call happens
    before any call to free() and before any call to a destructor (if any)
    is done. For the calls that add an object the callback is called after
    the container has been modified.
    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 information13 for each operation.
    None of the arguments will be ever NULL or zero.
    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.
    CONTAINER ERROR BADARG The observed object pointer is NULL , the
    callback function pointer is NULL , or the operations argument is zero.
    CONTAINER ERROR NOMEMORY There is not enough memory to proceed.
    Returns:An integer greater than zero if the relationship was
    established, a negative error code otherwise.
    int (*Notify)(void *ObservedObject,unsigned Operation,
    void *ExtraInfo1,void *ExtraInfo2);
    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 function will receive. ”Notify” will call all the objects that
    are observing ObservedObject and that have subscribed to one of the
    operations 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.

    CONTAINER ERROR BADARG The ObservedObject pointer is NULL or the
    Operation argument is zero.

    Returns:A positive number with the number of objects that received the
    notifications, zero if there was no match for the combination of
    observed object and operations specified, or a negative error code.

    size_t (*Unsubscribe)(void *ObservedObject, ObserverFunction callback);
    This function breaks the relationship between the observed object and
    the observer. There are several combinations of both arguments:
    o 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.
    o 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.
    o If both callback and ObservedObject are non NULL , only the matching
    relationship will be removed from the tables.
    Here is a complete example that demonstrates some of the above functions.

    include "containers.h"
    static void fn(void *ObservedObject, unsigned operation,
    void *extraInfo[])
    printf("Object is %p, operation is %d\n",ObservedObject,operation);
    int main(void)
    ValArrayInt * vInt = iValArrayInt.CreateSequence(24,0,1);
    printf("Original array: \n");
    iValArrayInt.Fprintf(vInt,stdout,"%d ");
    printf("Adding an integer\n");
    iValArrayInt.Fprintf(vInt,stdout,"%d ");
    Original array:
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
    Adding an integer
    Object is 0x100100080, operation is 1
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 4096
    Object is 0x100100080, operation is 16

    We setup our observer function calling the Subscribe API. We request to
    be notified when there is an addition and when the object finalizes. Our
    callback function does nothing but print some of its arguments. We see
    that we get called when the requested operations are performed.
    jacob navia, Dec 10, 2013
    1. Advertisements

  2. My view on this is that it's a programming paradigm best avoided. A list
    supports insert / delete, not arbitrary operations triggered on insert/
    delete. Those operations are quite likely to themselves involve insert/
    delete (If you're not interested in the list, why attach a notify on it?
    If you are interested in the list, as often as not you'll want to modify
    it again in response to a modification). But the killer is we can stack up
    two observers on the same list, written by different people, who don't know
    about the other observer. If they're both modifying in response to
    modifications, or even if they're just modifying third party structures,
    you've got the possibility of all sorts of complex, hard to understand,
    undesireable interactions.

    So I'd tend to cut through the problem by discouraging it altogether.
    But if you're writing generic code, there's a strong case that you have to
    support everything that a calling programmer may want to do. That
    includes attaching observers to containers. By proving an interface,
    you're in a sense saying "write the code this way". I know not really.
    The question is how to provide something, but make it clear that in most
    circumstances, it shouldn't be used. There are other examples, eg you can
    globally override the new / delete operators in C++. Very rarely do people
    do this, but occasionally it's highly useful.
    Malcolm McLean, Dec 10, 2013
    1. Advertisements

  3. jacob navia

    jacob navia Guest

    Le 10/12/2013 23:46, Malcolm McLean a écrit :
    In principle you are right. It can get messy. But it is very usefull if
    you avoid inserting/deleting stuff within a callback.

    For instance, you have a pointer to the list in some data structure.
    If the object that owns the pointer is notified of the list destruction
    it just sets the pointer to NULL.

    Or, you want to be notified of deletions since you have just stored
    pointers in the list, not the data the pointers point to, and you
    want to call free when an object is deleted.

    Or you have a window list, and you want to redraw some part of your user
    interface when an object is added or deleted.

    All this can be very useful and it is perfectly safe.

    C is a "close to the hardware" language and C programmers aren't Java
    programmers where the programmer is protected from its own mistakes by a
    software layer that doesn't allow any pointers or dangerous constructs.

    Here the dangers of this construct are documented, so they are clear to
    the programmer.

    In any case the list serializes the messages to the registered
    observers, so there is only ONE observer being notified at a time.
    There is no "broadcast"
    jacob navia, Dec 10, 2013
    1. Advertisements

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.