RSS Feed

Peter Goodman's blog about PHP, Parsing Theory, C++, Functional Programming, Applications,

Observers and Dispatchers

NOTE: an ArrayIterator is used in the code below. Make sure to familiarize yourself with iterators before continuing.

Maybe you've used a framework that uses Observers and Dispatchers, or you've heard of them but don't know how they work. Well, I'm going to explain them and tell you why they're so useful.

First, what are the observer and dispatcher patterns? Well, just as their names imply, they are a set of classes that observe events and another set of classes that dispatch events. This might seem like a mouthful, so instead of giving you more explanations, I will show you how they work:

ClassExplanation
ObserverAn abstract class that other types of observers will inherit from.
Observer::notifyAbstract function that gets passed an event.
DispatcherA class that collects and notifies observers.
Dispatcher::addObserverAdd a 'FAObserver' object to an array of observers classified by the events that they notify (mouthful, don't worry)
Dispatcher::notifyAllNotify all of the observers of a given event that the event is taking place.
ObservableEventAn abstract class representing an event that is observable.

You need to take this example with a grain of salt because not all observers or dispatchers work this way and have all of the same functions. Otherwise, most of the above table will seem completely useless because half of the classes and functions are 'abstract'. This means that they cannot/should not be called directly and that other classes should extend these classes and implement the real logic. So, lets get to those other classes and functions!

First, we will create a NamedObserver. It's another abstract class so nothing really goes on in it. What is important about classes that extend the NamedObserver is that they have a name! Also, to get things to work smoothly, the name of the observer should correspond to the name of the event that it observes. The concept of 'observing' might still be a bit fuzzy, so just don't think too much about it yet.

class FAObserver {
    function notify(&$event) {
        assert(FALSE);
    }
}

class FANamedObserver extends FAObserver {
    function getObserverName() {
        assert(FALSE);
    }
}

Not much going on there. At least it's some code to look at though! The next thing we need to look at is an observable event. An observable event is really just an event. For example: I am doing a database query, the event in this case could be something like DatabaseQueryEvent. The name of the event could be 'db_query' and the observer that would watch it would be DatabaseQueryObserver with the name 'db_query'. I'm getting into this stuff pretty quickly, but the main point of this is to explain how events and observers are linked together. How about some code?

class FAObservableEvent {
}

class FANamedObservableEvent extends FAObservableEvent {
    function getEventName() {
        assert(FALSE);
    }
}

Woah, I'm on a roll: none of the code I keep showing you does anything yet! That's right, we're just working on the building blocks now.

What we have sofar are observers and observable events. Before I continue, I want to reiterate how they work together now that we have some code written. In order for a named observer to oberserve an named event, meaning: in order for an specific observer to be notified of a specific event, the NamedObserver::getObserverName() must be the same as the NamedObservableEvent::getEventName(). However, if you are not using named observer/events, you don't need to think or worry about 'getEventName' or 'getObserverName' at all. On to Observer::notify()!

The way an observer is told that the event has happened/is happening is through Observer::notify(). The event that is taking place is passed by reference to Observer::notify() and then that observer will do whatever it pleases when that is called.

Go forth and Dispatch!

class FADispatcher {
    var $_observers = array();
    
    function addObserver(&$observer) {
        assert(is_a($observer, 'FAObserver'));
        
        $this->_observers[] = &$observer;
    }
    
    function notifyAll(&$event) {
        assert(is_a($event, 'FAObservableEvent'));
        
        $ret = FALSE;
        $it = &new FAArrayIterator($this->_observers);
        
        while ($it->next()) {
            $observer = &$it->current();
            if ($ret = (bool)$observer->notify($event)) {
                break;
            }
        }
        
        return $ret;
    }
}

That's a lot of code to look at right away, so don't take it too seriously. Before I go on explaining how that works, a few things need to be taken into consideration: this is a general dispatcher and does not take advantage of the NamedObserver's or NamedObservableEvent's. This dispatcher will simply notify all observers attached to it regardless of the event. That's why after I've explained it, I will show you the code for our NamedDispatcher.

So, to better explain this, I will give a scenario. We have a database abstraction layer (DBA, a class that abstracts database-specific functions to common functions, e.g.: query, update, etc.). In our DBA class, we will add a variable '_dispatcher' that is a new instance of Dispatcher.

In our MySQL class (this class extends our DBA class), we have access to the '_dispatcher' variable. We will have a MySQL::Query() function that when called, notifies observers of an event. The way it does this is by using the Dispatcher::notifyAll() function, and in this case: $this->_dispatcher->notifyAll(). It will pass an event to the notifyAll() function. This event is a new instance of and ObservableEvent / NamedObservableEvent.

Take a second to suck that all in. I suggest reading that paragraph over again, a lot goes on. Before I get into the code, here's a nice table description of everything that happens:

  • DBA::_dispatcher becomes a new instance of Dispatcher
  • A new Observer is instantiated. It will be passed to Dispatcher::addObserver()
    • DBA::_dispatcher calls Dispatcher::addObserver( new Observer )
      • Dispatcher::addObserver() adds an observer to the stack of observers that will be notified of an event.
  • MySQL extends DBA, gaining access to DBA::_dispatcher
  • MySQL::Query() is called.
    • Inside MySQL::Query(), MySQL::_dispatcher is used.
    • A new ObservableEvent is instantiated. It will be passed to Dispatcher::notifyAll()
    • MySQL::_dispatcher calls Dispatcher::notifyAll( new ObservableEvent )
        Dispatcher:notifyAll loops over all of the observers added with Dispatcher::addObserver()
      • For each observer, Observer::notify( ObservableEvent ) is called with the ObservableEvent passed to it.

That was a lot. You should read it over twice. Now, on to some code that will help explain this example!

class DBA 
{
    var $_dispatcher;

    function DBA() 
    {
        $this->_dispatcher = &new FADispatcher;

        $this->_dispatcher->addObserver( new DatabaseQueryObserver() );
    }

    function addObserver(&$observer) 
    {
        $this->_dispatcher->addObserver($observer);
    }

    [...]

}

class MySQL extends DBA
{
    function Query($sql)
    {
        [...]

        $this->_dispatcher->notifyAll(new DatabaseQueryEvent() );
    }
}

In this example, 'DatabaseQueryObserver' would extend the Observer class and 'DatabaseQueryEvent' would extend the ObservableEvent class. Right now, this all seems pretty useless. But, assume you wanted to record information about each database query for later viewing (debugging, whatever), you could pass extra info to the DatabaseQueryEvent so that when it's passed to the DatabaseQueryObserver, it can figure stuff out! This is a good example of where NamedObservers are not needed. Now, let's go make a similar dispatcher, except that it can differentiate between different events.

class FANamedDispatcher {
    var $_observers = array();
    
    function addObserver(&$observer) {
        assert(is_a($observer, 'FANamedObserver'));
        
        $this->_observers[$observer->getObserverName()][] = &$observer;
    }
    
    function notifyAll(&$event) {
        assert(is_a($event, 'FANamedObservableEvent'));
        
        $ret = FALSE;
        
        if (isset($this->_observers[$event->getEventName()])) {
            $it = &new FAArrayIterator($this->_observers[$event->getEventName()]);
            
            while ($it->next()) {
                $observer = &$it->current();
                if ($ret = (bool)$observer->notify($event)) {
                    break;
                }
            }
        }
        
        return $ret;
    }
}

Well, NamedDispatcher almost mirrors Distpatcher! The main things to notice are the calls to Observer::getObserverName() and ObservableEvent::getEventName(). Instead of storing a numerical array of Observer classes, the FANamedDispatcher stores an associative array of observers, where the array keys are the observer names. Another thing to notice is that NamedObservers are pushed into a sub array of of the observers array. This means that there can be more than one different events, and there can be more than one observers per event.

When NamedDispatcher::notifyAll() is called, it looks to see if there is an array of observers for the NamedObserver::getEventName(). If any observers with the same name as the event exist, then it will loop over those observers and notify then with NamedObserver::notify(). The event object is passed through the notify() function so that the observer has access to any data passed to the NamedEvent when it was instantiated.

How about some examples of NamedObserver's and NamedObservableEvent's? Well, I don't have any now. But, the usage of NamedObserver's and NamedObservableEvent's is very similar to normal Observer's and ObservableEvent's, with the exception that the named functions have functions that return their names.


Comments


Comment