Le design pattern Observer avec la spl library

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

403892

  • 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.

Rédigé par

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.