Rappelez-vous, dans l’article précédent, j’ai présenté le design pattern Observer, mais avec une implémentation propre. En voici l’implémentation avec la Bibliothèque standard Spl.
Les changements
- Suppression de l’interface Observer (remplacée par l’interface SplObserver)
- La classe abstraite Observable implémente l’interface SplSubject
- Les fonctions de la classe abstraite Observables changent de nom: addObservers devient attach, notifyObserver devient notify
Mis à part cela… eh bien, vous allez le voir, rien ne change!
1) Créons la classe abstraite Observable (le sujet)
namespace DP\SplObserverBundle\AbstractClass; use Doctrine\Common\Collections\ArrayCollection; abstract class Observable implements \SplSubject { /** * * @var arrayCollection */ private $observers; private $changed; public function __construct() { $this->observers = new ArrayCollection(); $this->changed = false; } public function attach(\SplObserver $observer) { $this->observers->add($observer); } public function notify() { if ($this->hasChanged()) { foreach ($this->observers as $observer) { $observer->update($this); } } $this->clearChanged(); } public function detach(\SplObserver $observer) { $this->observers->remove($observer); } public function hasChanged() { return $this->changed; } public function setChanged() { $this->changed = true; } public function clearChanged() { $this->changed = false; } }
2) La classe concrète Observable: Données Meteo
namespace DP\SplObserverBundle\Entity\Observable; use DP\SplObserverBundle\AbstractClass\Observable; /** * DonneesMeteo => celui qui est observé */ class DonneesMeteo extends Observable { private $temperature; private $humidity; private $pressure; function getTemperature() { return $this->temperature; } function getHumidity() { return $this->humidity; } function getPressure() { return $this->pressure; } public function setMesures($temperature, $humidity, $pressure) { $this->temperature = $temperature; $this->humidity = $humidity; $this->pressure = $pressure; $this->setChanged(); $this->notify(); } }
3) Les observers: AffichageConditions, AffichagePrevisions, AffichageStats
namespace DP\SplObserverBundle\Entity\Observer; /** * AffichageConditions * */ class AffichageConditions implements \SplObserver { private $temperature; private $humidity; private $pressure; function getTemperature() { return $this->temperature; } function getHumidity() { return $this->humidity; } function getPressure() { return $this->pressure; } public function __construct(\SplSubject $observable) { $observable->attach($this); } public function update(\SplSubject $observable) { if ($observable instanceof \DP\SplObserverBundle\Entity\Observable\DonneesMeteo) { $this->temperature = $observable->getTemperature(); $this->humidity = $observable->getHumidity(); $this->pressure = $observable->getPressure(); } } public function getNewValues() { return array('temperature' => $this->temperature, 'humidite' => $this->humidity, 'pression' => $this->pressure); } }
namespace DP\SplObserverBundle\Entity\Observer; /** * AffichageConditions * */ class AffichagePrevisions implements \SplObserver { private $currentPressure; private $lastPressure; private $prevision; function getCurrentPressure() { return $this->currentPressure; } function getLastPressure() { return $this->lastPressure; } function getPrevision() { return $this->prevision; } public function __construct(\SplSubject $observable, $currentPressure) { $this->currentPressure = $currentPressure; $observable->attach($this); } public function update(\SplSubject $observable) { if ($observable instanceof \DP\SplObserverBundle\Entity\Observable\DonneesMeteo) { $this->lastPressure = $this->currentPressure; $this->currentPressure = $observable->getPressure(); } } public function getNewValues() { if ($this->currentPressure > $this->lastPressure) { $this->prevision = "Improving weather on the way!"; } else if ($this->currentPressure == $this->lastPressure) { $this->prevision = "More of the same"; } else if ($this->currentPressure < $this->lastPressure) { $this->prevision = "Watch out for cooler, rainy weather"; } return array('prevision' => $this->prevision); } }
namespace DP\SplObserverBundle\Entity\Observer; /** * AffichageConditions * */ class AffichageStats implements \SplObserver { private $maxTemp; private $minTemp; private $sumTemp = 0.0; private $numReadings; public function __construct(\SplSubject $observable, $minTemp, $maxTemp) { $this->minTemp = $minTemp; $this->maxTemp = $maxTemp; $observable->attach($this); } function getMaxTemp() { return $this->maxTemp; } function getMinTemp() { return $this->minTemp; } function getSumTemp() { return $this->sumTemp; } public function update(\SplSubject $observable) { if ($observable instanceof \DP\SplObserverBundle\Entity\Observable\DonneesMeteo) { $temp = $observable->getTemperature(); $this->sumTemp += $temp; $this->numReadings++; if ($temp > $this->maxTemp) { $this->maxTemp = $temp; } if ($temp < $this->minTemp) { $this->minTemp = $temp; } } } public function getNewValues() { $temperature = ($this->sumTemp / $this->numReadings) . "/" . $this->maxTemp . "/" . $this->minTemp; return array("AvgMaxMinTemperature" => $temperature); } }
4) Le contrôleur, qui réunit tout le monde
namespace DP\SplObserverBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use DP\SplObserverBundle\Entity\Observable\DonneesMeteo; use DP\SplObserverBundle\Entity\Observer\AffichageConditions; use DP\SplObserverBundle\Entity\Observer\AffichageStats; use DP\SplObserverBundle\Entity\Observer\AffichagePrevisions; class DefaultController extends Controller { /** * @Route("/", name="spl_observer") * @Template() */ public function indexAction() { $donneesMeteo = new DonneesMeteo(); // sujet, celui qui est observé $affichageConditions = new AffichageConditions($donneesMeteo); // l'observateur, à qui on passe l'observé en argument $affichageStats = new AffichageStats($donneesMeteo, 10, 30); // l'observateur, à qui on passe l'observé en argument $affichagePrevisions = new AffichagePrevisions($donneesMeteo, 29.2); // l'observateur, à qui on passe l'observé en argument $donneesMeteo->setMesures(25, 10, 1200); // le sujet met à jour ses données return array( 'affichageConditions' => $affichageConditions->getNewValues(), 'affichageStats' => $affichageStats->getNewValues(), 'affichagePrevisions' => $affichagePrevisions->getNewValues() ); } }
Pas énormément de changements, mais nous avons tout de même une interface de moins, ce qui est toujours appréciable.
Alors, au final, pourquoi utiliser l’une plutôt que l’autre? Je dirais que ça dépend si vous voulez rajouter des fonctions obligatoires dans votre interface ou si le comportement standard vous convient.
Comme d’habitude, le dépôt github: https://github.com/jpsymfony/dp-observer.git.
Le répertoire correspondant est SplObserverBundle.