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 {
|
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 {
|
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 {
|
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 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 {
|
This outputs the following:
Logger: logging: Object id #2 / code: 0 PartnershipRelay: informing partner: error code: 0 OKIf 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: 3The 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 {
|
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
protected function notify($message) {
but then call it like so
$this->notify($this, "successful upload");
why are you passing two arguments here? it fails for me on 5.2.1. but then, so does the "$obj" trick.