Today's pattern is the Observer one. The intent of this pattern is breaking a [usually one-to-many] dependency between objects by making the set of objects act as Observers of a Subject one, which notifies them when its state changes or is affected someway.
The result of the application of the Observer pattern is an effective reduction of coupling: often notifications are sent to disparate objects which are thus prevented to becoming entangled.
This pattern is a form of publish/subscribe, where a Mediator between publishers and subscriber is not involved.
The classic application of the Observer pattern is in maintaining synchronization between views of the same data (part of the Mvc paradigm).
Another useful application is the connection of external infrastructure whose existence the subject should not rely on at all. For example Subject can be an object from the domain layer, which accepts as Observer-implementors objects from the upper layers (or their Adapters), to send them events to catch. The Observer and Subject interfaces in this case reside in the domain layer.
There is a broad spectrum where the implementation of this pattern can fit, whose extremes are named push and pull styles.
- In the pure push style, the Subject passes only the necessary parameters in the notification: this solution favors encapsulation of the Subject data.
- In the pure pull style, the whole Subject is passed to the update() method: this solution favors reusability of the Observers on different implementations or subclasses of Subject.
Another variability factor in implementation is how references are kept between the various objects of the graph. The only mandatory field references are the Observer collection, which is maintained in the Subject. The only advantage of keeping a reference to Subject in the Observer implementations is to not pass it in the notification, or check that the passed object is the subject where it was registered. Though, a mutual association would have to be maintained, and this can be tricky.
Participants
- Subject: interface that defines a protocol to set up a collection of Observers. Can sometimes be omitted by using the ConcreteSubject directly in method signatures.
- Observer: interface that defines a notification protocol.
- ConcreteSubject: stores the Observers and notifies them according to a contract, usually when its state changes (or when a notify() method is called).
- ConcreteObserver: performs business logic operations upon a notification from a Subject is received.
<?php /** * Needless to say, the Observer participant. * The chosen style is pure pull, so the whole User object * is passed to update(). User is en entity in the domain model * so the Observer will be probably dependent on it anyway. * The gained advantage is that User do not know the concrete * class of any of its Observers. */ interface UserObserver { public function update(User $subject); } /** * Subject interface is omitted. The ConcreteSubject * is used instead directly. */ class User { protected $_nickname; protected $_observers = array(); protected $_status; public function __construct($nickname) { $this->_nickname = $nickname; } public function getNickname() { return $this->_nickname; } /** * Accepts an observer. Note that we really don't need * a detach() method. */ public function attach(UserObserver $observer) { $this->_observers[] = $observer; } /** * Updates the status of the user (and notifies Observers.) */ public function setStatus($text) { $this->_status = $text; $this->_notify(); } public function getStatus() { return $this->_status; } /** * May be public if you want the Client to control * the notification generation. */ protected function _notify() { foreach ($this->_observers as $observer) { $observer->update($this); } } } /** * This ConcreteObserver is passed the User. * It can extract from the argument the data it needs (pulling). */ class TwitterStatusObserver implements UserObserver { /** * We should send the new status to twitter * which will mirror it, but to keep this a standalone * code sample we'll simply print it. */ public function update(User $user) { $nickname = $user->getNickname(); $status = $user->getStatus(); echo "$nickname has changed its status to \"$status\"\n"; } } // Client code $user = new User('Giorgio'); $user->attach(new TwitterStatusObserver); $user->setStatus('writing PHP code');
Ho trovato l'esempio proposto molto interessante, ti è già capitato di usarlo in qualche applicazione o l'hai scritto ad-hoc per il post?
ReplyDeleteMi chiedevo se (pensando al Domain-Driven Design) aggiungere il metodo _attach_ all'interfaccia di User non _sovraccarichi_ in qualche modo le responsabilità dell'Entità.
Era un esempio classico, ma scrivo tutto il codice per i post da zero per mantenerlo breve e sintetico.
ReplyDeleteDel resto se User deve notificare quando un suo membro *privato* cambia almeno un metodo per accettare gli Observer lo deve avere. Altrimenti potresti mettere il metodo su un servizio (ugly), oppure produrre un Decorator per User che si occupi delle notifiche (pero' in questo caso e' un overkill.)
(however, this blog's official language is English :)
It's very interesting article. Thank you for information.
ReplyDeleteThanks for the reply and the suggestion :)
ReplyDeleteI totally agree with you when you say that using a Decorator to add a Subject behaviour to the User would be a overkill. But, you know, I'm kinda perfectionist so I tend impose to myself a really strict set of rules and try my best to follow them (that's the major reason because all my projects never see the light).
Keep going with these great articles, although I have the Design Patterns book from the Gof, I find them really helpful for improving my understanding.
I apologize for writing my previous comment in Italian, I'm not really keen in English.
Ot: It would be nice to meet sometimes to discuss about Software Development in general in person.
This is interesting piece of code. I like your style!
ReplyDeleteIsn't the example pure /pull/, since you're passing the whole User object?
ReplyDeleteMy mistake, the docblock remained there after I refactored the code. In fact, in the post I stated: The code sample implements a pull-version of the pattern.
ReplyDeleteThanks for the signalation.