|
Table of Contents This article looks at the Observer/Subject design pattern using interfaces bundled with PHP version 5.1 and higher. This pattern is sometimes known as a Listener pattern and is useful for facilitating communication between objects—it’s a way of informing interested parties when relevant changes have occurred. A simple example of this pattern is implemented to help understand its use. As implemented in PHP, the Observer/Subject pattern incorporates the
SplSubject and SplObserver interfaces—the ‘
Since the SplSubject is an interface, all its methods are public and must be defined by any class that implements it. The purpose of the Through the As always with an interface, the details of implementation are left up to the individual programmer. The SplObserver interface is much simpler and defines only one method:
Again, type hinting ensures that only a class that implements the
subject interface can be passed in. The The observer class is the simpler of the subject/observer pair. In the example code in this article, two observers are created, a doctor and a derived class, the neurosurgeon. Using two observer classes gives better insight into how this design pattern can be used. The Doctor class represents a general practitioner. It is made up of
one data member, the doctor's name, the required observer While constructors are usually a requirement, the The To view the code see Example A.1, “The SplObserver Class – Doctor.php”. The Neurosurgeon class is even simpler than the Doctor class. This
class has one method, the As with any derived class, the behaviour of the derived class is similar but slightly different from the parent class. A Neurosurgeon won't be interested when the patient's state has no bearing on its speciality, so unlike the general practitioner, does not react when the patient gets a cold. The neurosurgeon has all the attributes of the Doctor class, but
overrides the Doctor's implementation of the To view the code see Example A.2, “The Derived SplObserver Class – Neurosurgeon.php”. The subject is the more interesting interface given that it must
implement three methods. It must implement Given that patients often have more than one doctor, it makes sense to
store a reference to each observer in an associative array, declared as a
class level variable, in this case the This class is discussed in detail in the following sections but for a complete code listing see Example A.3, “The SplSubject Class – Patient.php”. As noted, type hinting the parameter to the At first glance the way to attach an observer seems quite simple. To ensure that the same observer isn’t added twice you might think that the following code would suffice:
if(!in_array($o, $this->observers)){
$this->observers[] = $o;
}
Let's see why this approach is inadequate. The problem with using the The The Identical logic is used for detaching an observer so the
As an SplSubject the Patient class must implement the Apart from the magic methods—which function in the same way as the
magic methods of the Doctor class—the only other methods of the Patient
class set and get the state of the patient. A serious implementation of
a patient class would probably want to use a more robust way of
changing the patient's state, perhaps using a The main portion of the code (see Example A.4, “Main”) shows how these three
classes interact. A doctor, a neurosurgeon and a patient are created. The
doctor and neurosurgeon are attached to the patient. An attempt to attach
the doctor twice outputs a message indicating that the doctor is already
an observer, indicating that the The output is displayed below: Creating Doctor Hardcastle. Creating Neurosurgeon Cranium. Creating Patient Ivy. Adding Doctor Hardcastle as an observer. Adding Doctor Hardcastle as an observer. Doctor Hardcastle is already an observer. Adding Neurosurgeon Cranium as an observer. Patient Ivy has a cold. Doctor Hardcastle says, 'Rest in bed.' Patient Ivy has a headache. Doctor Hardcastle says, 'Take an Aspirin.' Neurosurgeon Cranium says, 'This requires brain surgery'. Neurosurgeon Cranium is no longer an observer. Patient Ivy has a headache. Doctor Hardcastle says, 'Take an Aspirin.' This implementation of the observer and subject classes may not reflect a real-life programming problem, but it is illustrative of how the subject/observer pattern might be used:
A. Code AppendixExample A.1. The SplObserver Class – Doctor.php
<?php
class Doctor implements SplObserver{
protected $name;
public function __construct($name){
$this->name = $name;
}
/**
Magic method is inherited
*/
public function __toString(){
return get_class($this) . " " . $this->name;
}
/**
Implement the one method of SplObserver
*/
public function update(SplSubject $s){
if ($s->getCold()== true){
echo " $this says, 'Rest in bed.'<br />";
}
if ($s->getHeadache()== true){
echo " $this says, 'Take an Aspirin.'<br />";
}
}
}
?>
Example A.2. The Derived SplObserver Class – Neurosurgeon.php
<?php
/**
Derived SplObserver class
*/
class Neurosurgeon extends Doctor{
/**
Override the inherited implementation of update
*/
public function update(SplSubject $s){
if ($s->getHeadache() == true){
echo " $this
says, 'This requires brain surgery'.<br />";
}
}
}
?>
Example A.3. The SplSubject Class – Patient.php
<?php
class Patient implements SplSubject{
private $name;
/**
Array of Observers
*/
private $observers = array();
/**
Patient state variables
*/
private $cold = false;
private $headache = false;
public function __construct($name){
$this->name = $name;
}
public function __toString(){
return get_class($this) . " " . $this->name;
}
/**
Implement SplSubject attach method
*/
public function attach(SplObserver $o){
$elements = array_keys($this->observers, $o);
$notinarray = true;
foreach($elements as $value){
//objects with the same attributes can be distinguished
//using identity operator but derived class with same
//attributes will be different
if($o === $this->observers[$value]){
$notinarray = false;
break;
}
}
//check if there already
if($notinarray){
$this->observers[] = $o;
}else{
echo "$o is already an observer.<br />";
}
}
/**
Implement SplSubject detach method
*/
public function detach(SplObserver $o){
//use the same logic to detach
$elements = array_keys($this->observers, $o);
foreach($elements as $value){
//objects with the same attributes can be distinguished
//using identity operator
//but derived class with same attributes will be different
if($o === $this->observers[$value]){
unset($this->observers[$value]);
echo "$o is no longer an observer. <br />";
break;
}
}
}
/**
Implement SplSubject method
*/
public function notify(){
foreach ($this->observers as $value){
$value->update($this);
}
}
/**
Patient-specific set/get methods
*/
public function setHeadache($bool){
$this->headache = $bool;
if ($bool == true){
echo "$this has a headache.<br />";
}
$this->notify();
}
public function getHeadache(){
return $this->headache;
}
public function setCold($bool){
$this->cold = $bool;
if ($bool == true){
echo "$this has a cold.<br />";
}
$this->notify();
}
public function getCold(){
return $this->cold;
}
}
?>
Example A.4. Main
<?php
include 'Patient.php';
include 'Doctor.php';
include 'Neurosurgeon.php';
$doctor = new Doctor("Hardcastle");
echo "Creating $doctor.<br />";
$neurosurgeon = new Neurosurgeon("Cranium");
echo "Creating $neurosurgeon.<br />";
$patient = new Patient("Ivy");
echo "Creating $patient.<br />";
$patient->attach($doctor);
//can't attach the same doctor twice
$patient->attach($doctor);
$patient->attach($neurosurgeon);
$patient->setCold(true);
$patient->setCold(false);
//patient gets a headache
$patient->setHeadache(true);
//headache gone
$patient->setHeadache(false);
//get rid of neurosurgeon
$patient->detach($neurosurgeon);
//next headache, only the doctor responds
$patient->setHeadache(true);
?>
About the AuthorPeter Lavin has been published in a number of magazines and online sites, including UnixReview.com, php|architect and International PHP Magazine. He is a contributor to the recently published O'Reilly book, PHP Hacks and is also the author of Object Oriented PHP, published by No Starch Press. Please do not reproduce this article in whole or part, in any form, without obtaining written permission. |
||
|
|
||