Categories


Loading feed
Loading feed
Loading feed

PHP Patterns: The Observer Pattern (continued)


[ continued from part 1 of PHP Patterns: The Observer Pattern ]

Implementation

The Observer pattern removes the dependence of a subject class upon the components with which it must communicate. It does this by reversing the dependency relationship. The interested objects must opt in in order to receive notifications. They only guarantee that part of their interface concerned with receiving messages from the subject. This means that the subject (UploadManager in this case) needs no specialized knowledge of its observers. It must store references to them, and call an update() method on each one, but this is as far as the relationship need go it's a relationship of quantity rather than quality.

In our example, our UploadManager class was inappropriately intimate with three other classes: Logger, Informer and PartnershipRelay. To turn these into Observer classes, we either need to have them extend a common superclass or implement a common interface. The latter option gives us much more flexibility. It guarantees type and functionality without preventing the classes from subclassing different parents.

An observer interface is typically very simple:

interface UploadObserver {

    function
update(UploadManager $man, $message);
}

In this case, our Observer classes must implement an update() method. The subject passes a reference to itself along to update() together with a $message string. The observer class can use this reference to call methods on the subject in order to acquire further state information. Here's a dummy implementation of UploadObserver:

class PartnershipRelay implements UploadObserver {

    function
update(UploadManager $obs, $msg) {

        if (!
$obs->status()) {
            print
__CLASS__.": informing partner: error code: {$obs->status()}\n";
        }
    }
}

Notice the way that the update() method calls back to the UploadManager class, invoking its status() method.

So how is an Observer class invoked? The subject stores references to any interested observers and cycles through the list, calling update() UploadManager modified to act as a subject for observers:

class UploadManager {

    private
$user;
    private
$repDir;
    private
$status = 0;
    private
$observers = array();

    const   
UM_UPLOAD_ERROR = 1;
    const   
UM_QUOTA_ERROR  = 2;

    function
__construct(User $user, $repository) {
        
$this->user = $user;
        
$this->repDir = $repository;
    }

    function
attach(UploadObserver $obs) {
        
$this->observers["$obs"] = $obs;
    }

    function
detach(UploadObserver $obs) {
        
delete($this->observers["$obs"]);
    }

    protected function
notify($message) {

        foreach (
$this->observers as $obs) {
            
$obs->update($this, $message);
        }
    }

    function
processFile(UploadFile $file) {

        
$file->moveTo($this->repDir);

        
$this->assert(($file->size() < $this->user->available()), 'User quota exceeded', self::UM_QUOTA_ERROR);

        
$this->assert((!$errorcode = $file->errored()), "File upload error: $errorcode", self::UM_UPLOAD_ERROR);

        
$this->status = 0;
        
$this->notify($this, "successful upload");
        
$this->user->setStored($this->user->stored()+$file->size());
        return
true;
    }

    function
assert($condition, $msg, $code) {

        if (!
$condition) {
            
$this->status = $code;
            
$this->notify($this, "$msg ($code)");
            throw new
UploadFileException($msg, $code);
        }
    }

    function
status() {
        return
$this->status;
    }
}

The key methods to look at here are attach(), detach(), and notify(). attach() and detach() handle adding and removing observers. We use a little trick here. Objects quoted in string context resolve to a unique identifier (even if __toString() is defined). You can use this fact to build keys for an associative array. The notify() method cycles through all attached observers, calling update() on each. The UploadManager class calls notify() whenever it has something important to report on upload and on error, in this case.

So we have lost no functionality here in fact we have gained the opportunity to add lots more functionality without the need to amend the UploadManager class to accommodate it. UploadManager no longer has any specific knowledge of its observers apart from the fact that they implement UploadObserver and therefore have callable update() methods. Figure 4 illustrates the clean up.

Figure 4

Figure 4 may look complicated at first glance, but the relationships are really very simple. We might add an Informer object to an UploadManager using attach(). Then, when a user uploads a music file, UploadManager invokes notify(). notify() cycles through all UploadObserver objects in its list, including, of course, our Informer object. It calls update() on each one, passing on a reference to itself and a message string. Informer::update() needs more information about the UploadManager object's state, so it uses the provided reference to call status(). If an error status is found, Informer knows to send a warning mail to an administrator.

In design terms, this is a radical change. UploadManager now deals only with the UploadObserver type, whereas before it had to know about many different classes, each with subtle differences. It is thus decoupled from these classes, boosting reusability. You can also add new observers at runtime, which makes the class extensible.

As before, let's run our classes through their paces:

try {
    
$user = new User(4000);
    
$mgr = new UploadManager($user, '/tmp');

    
$mgr->attach(new Informer());
    
$mgr->attach(new Logger());
    
$mgr->attach(new PartnershipRelay());

    
$file = new MockUploadFile("test.gif");
    
//$file->setErrorCode(UPLOAD_ERR_PARTIAL);
    //$file->setSize(5000); // TOO HIGH!!

    
if ($mgr->processFile($file)) {
         print
"OK\n";
    }

} catch (
Exception $e) {
    print
"error: ".$e->getMessage()."\n";
}

This outputs the following:

Logger: logging: Object id #2 / code: 0
PartnershipRelay: informing partner: error code: 0
OK
If we uncomment one of the mischievous sabotage lines however, the output looks something like this:
Informer: mailing warning: Object id #2 / code: 1
Logger: logging: Object id #2 / code: 1
error: File upload error: 3
The observer objects responding to the subject's state, rather than any decision-making in the subject, determine the difference in behavior.

Discussion

The Observer pattern is not without its variations and issues.

One issue to address is the extent to which you generalize observer and observed behavior. It is tempting to create interfaces that define observer functionality at the most general possible level. You might create Observer and Observed interfaces, for example:

interface Observer {

    function
update(Observed $observed, $msg);
}

interface
Observed {

    function
attach(Observer $observer);

    function
detach(Observer $observer);

    function
notify();
}

This is a satisfying approach, and avoids the iffy smell conjured by multiple separate and repetitious Observer implementations. It can mean extra work, however. The Observer::update() method can only require a Observed object. When the time comes to implement update() the signature may be of limited use, because all you're guaranteed in the Observed object are attach(), detach() and notify() methods. These won't help you query the subject's state. You will need to use the instanceof operator to test the type of the $observed argument, only acting on the call if the right Observed object has been provided and the expected state methods are therefore present. By creating a specific observer implementation for UploadManager, on the other hand, I was able to do all my type checking in the UploadObserver::update() method.

In some circumstances, of course, the generic approach could work for you. You may create observers that behave in different ways according to the Observed subtype they are passed for example. Certainly, a Logger class will want to talk to instances of many different classes in a system. Ultimately the needs of your system will determine how generic you should make your Observer classes.

In this article, I have used a 'pull' (GOF) approach. This is neat, because you don't need to think too hard ahead of time about the information you pass to your observer objects. Instead you just pass them an instance of the observed class and let them call state methods to find out where things stand. An alternative is to use a 'push' method, in which you provide more information in the call to update(). The disadvantage of this is that you end up passing round information that isn't always used, and you need to be clear ahead of time as to which information is relevant. Changing the argument list to accommodate the needs of a new observer could involve adjustments to existing classes. On the other hand, the pull model has its own problems. In order to allow observers to glean useful state information, you may find yourself exposing some of the subject class' guts that you would otherwise have kept private.

Another variation on this pattern centers on the notify() method. I have had the subject class call this when anything of import occurs. However you can also expose the method, and allow client classes to call it after having worked with the subject. This can be useful in cases where many small changes of state take place. A client class calling methods on the subject can wait until it has finished a block of work before calling notify(), just as a user might wait until she has finished a set of changes before invoking commit on a version control system. Unfortunately, by relinquishing responsibility for calling notify(), you lose any guarantee that the method is called at all. This is a serious drawback: we can no longer ensure that all components that register will receive the updates they expect. The nature of your application should be your guide here, although on the whole I would favor making the subject call its own notify() method.

Typical Pattern Characteristics

The Observer pattern illustrates some core pattern objectives and principles. It is driven by the need to divorce a component from too intimate a knowledge of its peers. In decoupling the component from context you make your code less error prone and more extensible.

By moving interested components to implement a common interface, we confirm the primacy of interface over implementation. The subject class uses the observer interface as a means of broadcasting its updates, no longer concerned with individual subtypes and their various strategies for communication.

We can also see the importance of composition in this pattern. The subject class aggregates observers, and in so doing it provides a mechanism by which very different effects can be generated at runtime. Behind the observer interface we can create new observer implementations and drop them into place without changing the subject itself. Many of the best patterns share this characteristic favoring flexibility through composition at runtime over static, hard-coded functionality.

About the Author

Matt Zandstra is a developer, teacher and writer specializing in object-oriented Internet applications. He currently works as an engineer at Yahoo! where he helps develop a core template management system. He is the author of SAMS Teach Yourself PHP in 24 Hours, and the recently released PHP 5: Objects, Patterns and Practice (http://www.amazon.com/exec/obidos/tg/detail/-/1590593804). He has written articles on PHP for Linux Magazine, IBM, Zend.com and at bgz-consultants.com.

Comments


Monday, May 7, 2007
COUPLE SMALL POINTS
2:15AM PDT · tater
Friday, December 14, 2007
SPL KNOWS ABOUT OBSERVER PATTERN
7:00AM PST · doctorrock
Tuesday, April 1, 2008
THE "$OBS" TRICK
3:55AM PDT · aercolino