Implementing the observer pattern in C

J

jacob navia

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
 
J

jacob navia

Le 28/03/11 00:25, christian.bau a écrit :
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 :)
 
J

jacob navia

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.
 
J

jacob navia

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.
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,521
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top