Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum. Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

DESIGN PATTERNS [3]


Information sur le tutorial

Catégorie :Class et Objet ( POO ) Date de création : 11/10/2007 13:37:44 Vu : 7 645 fois

Note :
Aucune note

Commentaire sur cette source (14)
Ajouter un commentaire et/ou une note

Description

DESIGN PATTERNS (ou MOTIFS DE CONCEPTION) - PART 3


INTRODUCTION
J'ai décidé d'écrire ce tuto sur les design patterns car si l'on trouve de nombreuses références à ces méthodes de programmation sur le net,
la plupart du temps elles sont en anglais. Or, tout le monde n'est pas à l'aise avec la langue de Shakespeare ;-)
Je ne couvrirai pas l'exhaustivité des design patterns existants, mais je vais tâcher de me concentrer sur les plus fréquemment utilisés.
Les exemples de code seront écrits en PHP5, parce que...ben parce qu'on en est bientôt à PHP6 alors il serait temps
d'abandonner PHP4 ! ;-)


UN DESIGN PATTERN, QU'EST-CE DONC ??
Depuis que le monde est monde (ou presque...), les programmeurs se sont heurtés à des problèmes récurrents. Ils ont doncréflêchi, et ont mis en place des méthodes pour résoudre ces problèmes. Les design patterns sont ces méthodes.
Vous avez probablement déjà utilisé des design patterns sans le savoir! Par vous-même, ou en ayant copié un bout de code.
Mais il est bon de connaître ces méthodes et de les utiliser de manière académique : votre code en deviendra plus lisible pour les autres programmeur, au fait de ces méthodes. De plus, à quoi bon réinventer la roue ? Des programmeurs chevronnés ont eu, avant vous, les mêmes problèmes que vous, y ont réflêchi, et ont trouvé des parades : ces méthodes sont éprouvées, car elles sont été retournées dans tous les sens depuis bien des années.


LE VIF DU SUJET!
J'ai commencé dans la 1ère partie par les design patterns de type CREATION, puis dans la 2de, les design patterns de type STRUCTUREL. Ce tuto va s'occuper des design patterns de type COMPORTEMENT.
Cette dernière partie est un peu plus longue que les précédentes, et les exemples plus complexes. J'estime, si vous avez lu les précédents, que vous êtes maintenant suffisament aguerris :-) Mais il y a ici des design patterns particulièrement sympas!

Tutorial

DESIGN PATTERNS (ou MOTIFS DE CONCEPTION) - PART 3



INTRODUCTION

J'ai décidé d'écrire ce tuto sur les design patterns car si l'on trouve de nombreuses références à ces méthodes de programmation sur le net,

la plupart du temps elles sont en anglais. Or, tout le monde n'est pas à l'aise avec la langue de Shakespeare ;-)

Je ne couvrirai pas l'exhaustivité des design patterns existants, mais je vais tâcher de me concentrer sur les plus fréquemment utilisés.

Les exemples de code seront écrits en PHP5, parce que...ben parce qu'on en est bientôt à PHP6 alors il serait temps

d'abandonner PHP4 ! ;-)



UN DESIGN PATTERN, QU'EST-CE DONC ??

Depuis que le monde est monde (ou presque...), les programmeurs se sont heurtés à des problèmes récurrents. Ils ont doncréflêchi, et ont mis en place des méthodes pour résoudre ces problèmes. Les design patterns sont ces méthodes.

Vous avez probablement déjà utilisé des design patterns sans le savoir! Par vous-même, ou en ayant copié un bout de code.

Mais il est bon de connaître ces méthodes et de les utiliser de manière académique : votre code en deviendra plus lisible pour les autres programmeur, au fait de ces méthodes. De plus, à quoi bon réinventer la roue ? Des programmeurs chevronnés ont eu, avant vous, les mêmes problèmes que vous, y ont réflêchi, et ont trouvé des parades : ces méthodes sont éprouvées, car elles sont été retournées dans tous les sens depuis bien des années.



LE VIF DU SUJET!

J'ai commencé dans la 1ère partie par les design patterns de type CREATION, puis dans la 2de, les design patterns de type STRUCTUREL. Ce tuto va s'occuper des design patterns de type COMPORTEMENT.

Cette dernière partie est un peu plus longue que les précédentes, et les exemples plus complexes. J'estime, si vous avez lu les précédents, que vous êtes maintenant suffisament aguerris :-) Mais il y a ici des design patterns particulièrement sympas!



--COMPORTEMENT--



TEMPLATE METHOD

Le TEMPLATE METHOD (patron de méthode) est un design pattern très simple, que tous ceux programmant en POO PHP5 connaissent:

il s'agit de définir un template pour un ensemble de sous-classes. Ce template va se charger de déclarer et/ou d'implémenter des méthodes communes nécessaires à toutes ses sous-classes. Une méthode implémentée sera réutilisée telle quelle par les sous-classes; les méthodes simplement déclarées devront être implémentées dans les sous-classes en suivant la déclaration.

Pour ce faire, on utilisera les classes abstraites.

La classe abstraite (le TEMPLATE) définira comme FINAL les méthodes qu'il implémentera : il s'agit ici de fixer le template. Elles ne pourront donc pas être surchargées par les sous-classes.

Un petit exemple simple avec 2 jeux de dés où l'on a qu'une seule chance de jeter les dés : un 421, et un ou l'on doit faire 3 fois six :


<?php

/**

* Le TEMPLATE ! La classe abstraite parente

*

*/

abstract class threeDiceGames {

protected $aThrowResults = array();

protected $iNumberOfDice = 3;

final protected function throwDie() {

return mt_rand(1,6);

}

final protected function throwDice() {

for ($iCpt = 0; $iCpt < $this ->iNumberOfDice; $iCpt++) {

$this ->aThrowResults[] = $this ->throwDie();

}

}

final public function play() {

$this ->aThrowResults = array();

$this ->throwDice();

$this ->showResult();

if (true === $this ->checkHasWon()) {

$this ->won();

} else {

$this ->lost();

}

}

final protected function showResult() {

echo 'Résultats:<br />';

foreach ($this ->aThrowResults as $iResult) {

echo $iResult.'<br />';

}

}

abstract protected function checkHasWon();

abstract protected function lost();

abstract protected function won();

}


/**

* Une sous-classe héritant du TEMPLATE, et implémentant uniquement les méthodes abstraites. Les méthodes implémentées

* dans le TEMPLATE seront utilisées telles quelles, et ne peuvent être redéfinies

*

*/

class forTwoOne extends threeDiceGames {

protected function checkHasWon() {

if (in_array(4, $this ->aThrowResults) && in_array(2, $this ->aThrowResults) && in_array(1, $this ->aThrowResults)) {

return true;

}

return false;

}

protected function won() {

echo 'Bravo, vous avez gagné, vous avez fait 421!<br />';

}

protected function lost() {

echo 'Dommage, vous avez perdu, vous n\'avez pas fait 421...<br />';

}

}


/**

* Une autre sous-classe héritant du TEMPLATE

*

*/

class sixSixSix extends threeDiceGames {

protected function checkHasWon() {

if (18 === array_sum($this ->aThrowResults)) {

return true;

}

return false;

}

protected function won() {

echo 'Bravo, vous avez gagné, vous avez fait 3*6!<br />';

}

protected function lost() {

echo 'Dommage, vous avez perdu, vous n\'avez pas fait 3*6...<br />';

}

}


$forTwoOne = new forTwoOne;

$sixSixSix = new sixSixSix;

$forTwoOne ->play();

$sixSixSix ->play();

?>



STRATEGIE

La STRATEGIE permet de sélectionner un algorithme en fonction du contexte.

Elle se compose de :

Une STRATEGIE, qui est l'interface déclarant les actions des STRATEGIES CONCRETES

Des STRATEGIES CONCRETES

Un CONTEXTE qui va sélectionner la STRATEGIE adaptée au contexte courant.

J'ai, il y a quelques temps, publié un package de validation des données utilisateurs basé sur ce principe, bien plus complet que l'exemple ci-dessous. Il se trouve ici : http://www.phpcs.com/codes/PHP5-FORMCHECKER-VALIDATION-SAISIES-UTILISATEUR_40984.aspx

Et maintenant, l'exemple simple :


<?php

/**

* L'interface de STRATEGIE déclarant les actions des STRATEGIES CONCRETES

*

*/

interface strategie {

public function validate($mData);

}


/**

* Une STRATEGIE CONCRETE validant les emails

*

*/

class validateEmail implements strategie {

public function validate($mData) {

if (!preg_match ('`^[[:alnum:]]([-_.]?[[:alnum:]])*@[[:alnum:]]([-.]?[[:alnum:]])*\.([a-z]{2,4})$`', $mData)) {

return false;

}

return true;

}

}


/**

* Une STRATEGIE CONCRETE validant les url

*

*/

class validateUrl implements strategie {

public function validate($mData) {

if (!preg_match ('`((?:https?|ftp)://\S+[[:alnum:]]/?)`si', $mData) && !preg_match ('`((?<!//)(www\.\S+[[:alnum:]]/?))`si', $mData)) {

return false;

}

return true;

}

}


/**

* Une STRATEGIE CONCRETE validant les entiers

*

*/

class validateInt implements strategie {

public function validate($mData) {

if (!preg_match('`[0-9]+`', $mData)) {

return false;

}

return true;

}

}


/**

* Une STRATEGIE CONCRETE validant les chaines alphanumériques

*

*/

class validateAlphaNum implements strategie {

public function validate($mData) {

if (!preg_match('`[1-9a-zA-Z]+`', $mData)) {

return false;

}

return true;

}

}


/**

* La classe de CONTEXTE choisissant la stratégie à employer selon le contexte

*

*/

class dataValidator {

private $oStrategie;

public function setStrategie(strategie $strategie) {

$this ->oStrategie = $strategie;

}

public function validate($mData) {

if (is_string($mData) && trim($mData) === '') {

return false;

}

return $this ->oStrategie ->validate($mData);

}

}


/**

* On teste tout ça avec différentes données

*/

try {

$dataValidator = new dataValidator;

$mData = 'moi@home.net';

$dataValidator ->setStrategie(new validateEmail);

if (true === $dataValidator ->validate ($mData)) {

echo 'Valide';

} else {

echo 'Invalide';

}

echo '<br />';

$mData = 12345;

$dataValidator ->setStrategie (new validateInt);

if (true === $dataValidator ->validate ($mData)) {

echo 'Valide';

} else {

echo 'Invalide';

}

echo '<br />';

$mData = '';

$dataValidator ->setStrategie (new validateAlphaNum);

if (true === $dataValidator ->validate ($mData)) {

echo 'Valide';

} else {

echo 'Invalide';

}

} catch (Exception $e) {

echo $e;

}

?>



OBSERVATEUR

L'Observateur (observer en anglais) est un design pattern permettant d'écouter un objet et de réagir à certains évènements.

Tout l'intérêt est de pouvoir, sur 1 évènement donné, provoquer toute une série de réactions en cascade, un peu comme des triggers en SQL.

Il se compose de :

Un SUJET ABSTRAIT, l'interface qui déclare 3 méthodes nécessaires au SUJET CONCRET: attach(), detach() et notify()

Un SUJET CONCRET qui implémente le SUJET ABSTRAIT. Il s'agit de l'objet observé. On y intègre la gestion d'un état sur lequel vont se baser les OBSERVATEURS

Un OBSERVATEUR ABSTRAIT, l'interface qui déclare la méthode notify() nécessaire aux OBSERVATEURS

Un OBSERVATEUR CONCRET qui implémente l'OBSERVATEUR ABSTRAIT et réagit aux évènements qui le concerne.

Je vais reprendre un exemple que j'ai déjà publié dans les sources ici : http://www.phpcs.com/codes/PHP5-OBSERVER-DESIGN-PATTERN_43478.aspx

Il s'agit ici d'observer la vitesse d'une voiture... On y retrouve une classe déjà utilisée dans la partie 1 de ce tuto, SplObjectStorage. J'ai aussi réécrit toutes les interfaces internes à PHP5 au cas où elles n'existent pas sur votre version de PHP, et surtout pour que vous puissiez examiner leur code. Elles proviennent toutes de la SPL (http://www.php.net/~helly/php/ext/spl/)

De même, cet exemple utilise aussi un design pattern vu dans lea 2de partie de ce tuto, le COMPOSITE. Ici, il sert à stocker et manipuler tous les OBSERVATEURS liés au SUJET.

Voici l'exemple :


<?php

/**

* defining abstract implementations

*/

if (!interface_exists ('Countable')) {

/**

* Interface Countable

* @author: php

*

*/

interface Countable {

public function count();

}

}

if (!interface_exists ('SplObserver')) {

/**

* OBSERVATEUR ABSTRAIT

* Interface SplObserver

* @author: php

*

*/

interface SplObserver {

public function update(SplSubject $subject);

}

}


if (!interface_exists ('SplSubject')) {

/**

* SUJET ABSTRAIT

* Interface SplSubject

* @author: php

*

*/

interface SplSubject {

public function attach(SplObserver $observer);

public function detach(SplObserver $observer);

public function notify();

}

}


if (!class_exists ('SplObjectStorage')) {

/**

* Interface SplObjectStorage

* @author: php

*

*/

class SplObjectStorage implements Iterator, Countable {

/**

* Array of objects

*

* @var array

*/

private $storage = array();

/**

* Array of objects index

*

* @var int

*/

private $index = 0;

/**

* public function rewind

* Rewind array pointer

*

* @Return void

*/

public function rewind() {

rewind($this->storage);

}

/**

* public function valid

* Checks if current loop iteration is valid

*

* @Return boolean

*/

public function valid() {

return key($this->storage) !== false;

}


/**

* public function key

* Returns current key

*

* @Return int

*/

public function key() {

return $this->index;

}

/**

* public function current

* Returns current array entry

*

* @Return object

*/

public function current() {

return current($this->storage);

}

/**

* public function next

* Iterates to the next entry

*

* @Return void

*/

public public function next() {

next($this->storage);

$this->index++;

}

/**

* public function count

* Returns the number of entries in the array of objects

*

* @Return int

*/

public function count() {

return count($this->storage);

}

/**

* public function contains

* Checks if an object is in the collector array

*

* @Param object $obj : object to check

* @Return boolean

*/

public function contains($obj) {

if (is_object($obj)) {

foreach($this->storage as $object) {

if ($object === $obj) {

return true;

}

}

}

return false;

}

/**

* public function attach

* Attaches an object to the collector array

*

* @Param object $obj : object to attach

* @Return void

*/

public function attach($obj) {

if (is_object($obj) && !$this->contains($obj)) {

$this->storage[] = $obj;

}

}

/**

* public function detach

* Detaches an object to the collector array

*

* @Param object $obj : object to detach

* @Return void

*/

public function detach($obj) {

if (is_object($obj)) {

foreach($this->storage as $idx => $object) {

if ($object === $obj) {

unset($this->storage[$idx]);

$this->rewind();

return;

}

}

}

}

}

}


/**

* abstract class SplSubjectComposite implements SplSubject

* @author: Johan Barbier <johan.barbier@gmail.com>

*

*/

abstract class SplSubjectComposite implements SplSubject {

/**

* state of the subject

*

* @var mixed

*/

protected $state;

/**

* collector array of observers

*

* @var SplObjectStorage

*/

protected $aObservers;

/**

* public function __construct

* constructor

*

*/

protected function __construct () {

$this ->aObservers = new SplObjectStorage;

}

/**

* final protected function setState

* sets the object's state

*

* @param mixed $state

*/

final protected function setState ($state) {

$this ->state = $state;

}

/**

* final protected function getState

* gets the object's state

*

* @return mixed

*/

final protected function getState () {

return $this ->state;

}

/**

* public function attach

* Attaches an observer

*

* @Param SplObserver $observer : the observer to attach

* @Return boolean

*/

final public function attach(SplObserver $observer) {

if(!$this->aObservers->contains($observer)) {

$this->aObservers->attach($observer);

}

return true;

}

/**

* public function detach

* Detaches an observer

*

* @Param SplObserver $observer : the observer to detach

* @Return boolean

*/

final public function detach(SplObserver $observer) {

if(!$this->aObservers->contains($observer)) {

return false;

}

$this->aObservers->detach($observer);

return true;

}

/**

* public function notify

* Notify all the observers

*

* @Return void

*/

final public function notify() {

foreach($this->aObservers as $observer) {

$observer->update($this);

}

}

}

/**

* starting concrete implementations

*/

/**

* SUJET CONCRET

* class car extends SplSubjectComposite

* @author: Johan Barbier <johan.barbier@gmail.com>

*

*/

class car extends SplSubjectComposite {

/**

* Name of the car

*

* @var string

*/

private $sName;

/**

* State of the car

*

* @var int

*/


private $iSpeed = 0;


/**

* States constants

*

*/

const _STOPPED = 0;

const _STARTED = 1;

/**

* public function __construct

* constructor

* Initializes car's nname

*

* @param string $sName

*/

public function __construct ($sName) {

parent::__construct ();

$this ->sName = $sName;

}

/**

* public function start

* Starts the car and notify observers

*

* @Return void

*/

public function start () {

$this ->setState(self::_STARTED);

$this ->notify();

}

/**

* public function stop

* Stops the car and notify observers

*

* @Return void

*/

public function stop () {

$this ->setState(self::_STOPPED);

$this ->iSpeed = 0;

$this ->notify();

}

/**

* public function accelerate

* Accelerates the car and notify the observers

*

* @Param int $iAcceleration : how many Kmh you want to accelerate

* @Return void

*/

public function accelerate ($iAcceleration) {

if (self::_STOPPED === $this ->getState()) {

throw new Exception ('You must start the car before accelerating...');

}

if (!is_int ($iAcceleration) || $iAcceleration < 0) {

throw new Exception ('Wrong value for car::accelerate()');

}

$this ->iSpeed += $iAcceleration;

$this ->notify();

}

/**

* public function __get

* Get properties value

*

* @Param string $sProp : property's name

* @Return mixed

*/

public function __get ($sProp) {

switch ($sProp) {

case 'STATE' :

return $this ->getState();

break;

case 'SPEED' :

return $this ->iSpeed;

break;

case 'NAME' :

return $this ->sName;

break;

default :

throw new Exception ($sProp.' cannot be read');

}

}

/**

* public function __set

* Set properties value

*

* @Param string $sProp : property's name

* @Param mixed $mVal : property's value

* @Return void

*/

public function __set ($sProp, $mVal) {

throw new Exception ($sProp.' cannot be set');

}

}


/**

* OBSERVATEUR CONCRET

* class carStateObserver implements SplObserve

* @author Johan Barbier <johan.barbier@gmail.com>

*

*/

class carStateObserver implements SplObserver {

/**

* Stored subject's state

*

* @var int

*/

private $iSubjectState;

/**

* public function update

* updater. Echoes subject's state (if the car is stopped or started)

*

* @param SplSubject $subject

* @return void

*/

public function update(SplSubject $subject) {

switch ($subject ->STATE) {

case car::_STOPPED :

if (is_null ($this ->iSubjectState)) {

echo $subject ->NAME.' has not been started<br />';

} else {

echo $subject ->NAME.' has been stopped<br />';

}

$this ->iSubjectState = 0;

break;

case car::_STARTED :

if (1 !== $this ->iSubjectState) {

echo $subject ->NAME.' has been started<br />';

$this ->iSubjectState = 1;

}

break;

default :

throw new Exception ('Unexpected error in carStateObserver::update()');

}

}

}


/**

* OBSERVATEUR CONCRET

* class carSpeedObserver implements SplObserver

* @author Johan Barbier <johan.barbier@gmail.com>

*

*/

class carSpeedObserver implements SplObserver {

/**

* public function update

* updater. Echoes subject's speed

*

* @param SplSubject $subject

* @return void

*/

public function update(SplSubject $subject) {

if (car::_STOPPED !== $subject ->STATE) {

echo $subject ->NAME.' speed is '.$subject ->SPEED.'Kmh<br />';

}

}

}


/**

* OBSERVATEUR CONCRET

* class carOverspeedObserver implements SplObserver

* @author Johan Barbier <johan.barbier@gmail.com>

*

*/

class carOverspeedObserver implements SplObserver {

/**

* public function update

* updater. Catches the subject if it is overspeeding!

*

* @param SplSubject $subject

* @return void

*/

public function update(SplSubject $subject) {

if ($subject ->SPEED > 130) {

throw new Exception ('Speed limit is 130! You lost your driver license !');

}

}

}


/**

* Et maintenant, on teste tout ça :-)

*

*/

try {

$oCar = new car ('AUDI A4');

$oObs = new carStateObserver;

$oObs2 = new carSpeedObserver;

$oObs3 = new carOverspeedObserver;

$oCar ->attach($oObs);

$oCar ->attach($oObs2);

$oCar ->attach($oObs3);

$oCar ->start();

$oCar ->accelerate(20);

$oCar ->accelerate(30);

$oCar ->stop();

$oCar ->start();

$oCar ->accelerate(50);

$oCar ->accelerate(70);

$oCar ->accelerate(100);

$oCar ->accelerate(150);

} catch (Exception $e) {

echo $e ->getMessage ();

}

?>



MEMENTO

Le memento permet d'effectuer la sauvegarde de l'état d'une classe, et de restaurer cet état (un peu comme un rollback en SQL).

Il est composé de :

L'ORIGINATOR, qui est la classe pouvant être restaurée. Cette classe possède un état.

Le MEMENTO qui est chargé de la sauvegarde et de la restauration.

Le CARETAKER qui est chargé d'utiliser le MEMENTO.

Un petit exemple abstrait, avec la restauration de l'état de l'objet :


<?php

/**

* L'ORIGINATOR, la classe qui va être sauvegardée et restaurée

*

*/

class originator {

private $iState;

public function set($iState) {

if (!is_int($iState)) {

throw new Exception('State must be an integer');

}

$this ->iState = $iState;

echo 'Etat initialisée à '.$this ->iState.'<br />';

}

public function saveToMemento() {

echo 'Sauvegarde!<br />';

return new memento($this ->iState);

}

public function restoreFromMemento($oMemento) {

if (!$oMemento instanceof memento) {

throw new Exception(__METHOD__.'() : parameter given must be a memento object');

}

$this ->iState = $oMemento ->getSavedState();

echo 'Etat restauré via le memento : '.$this ->iState.'<br />';

}

}


/**

* Le MEMENTO qui se charge de sauvegarder l'état et de le retourner si demandé

*

*/

class memento{

private $iState;


public function __construct($iState) {

$this ->iState = $iState;

}

public function getSavedState() {

return $this ->iState;

}

}


/**

* Le CARETAKER qui fait les demandes au MEMENTO

*

*/

class caretaker extends ArrayIterator {

public function addMemento($oMemento) {

$this[] = $oMemento;

}

public function getMemento($iOffset) {

if(!$this ->offsetExists($iOffset)) {

throw new Exception('No saved memento at position '.$iOffset);

}

return $this[$iOffset];

}

}


try {

$oCaretaker = new caretaker;

$oOriginator = new originator;

$oOriginator ->set(1);

$oOriginator ->set(2);

$oCaretaker ->addMemento($oOriginator ->saveToMemento());

$oOriginator ->set(3);

$oCaretaker ->addMemento($oOriginator ->saveToMemento());

$oOriginator ->set(4);

$oOriginator ->restoreFromMemento($oCaretaker ->getMemento(1));

} catch(Exception $e) {

echo $e;

}

?>


Et un exemple concrêt à peine différent, au fond :


<?php

/**

* L'ORIGINATOR, la classe qui va être sauvegardée et restaurée

*

*/

class button {

private $sColor = 'white';

private $sText = 'Hello';

private $sBorder = 'black';

private $sState;

private $aUserFriendlyProps = array(

'COLOR', 'TEXT', 'BORDERCOLOR'

);

public function __set($sProp, $sVal) {

if (!in_array($sProp, $this ->aUserFriendlyProps)) {

throw new Exception ('Property '.$sProp.' cannot be set');

}

switch ($sProp) {

case 'COLOR':

$this ->sColor = $sVal;

break;

case 'TEXT':

$this ->sText = $sVal;

break;

case 'BORDERCOLOR':

$this ->sBorder = $sVal;

break;

}

}

public function __toString() {

$sHtml = '<span style="background-color:'.$this ->sColor.';border: 1px solid '.$this ->sBorder.';">'.$this ->sText.'</span><br />';

return $sHtml;

}

public function saveToMemento() {

echo 'Sauvegarde!<br />';

$this ->sState = serialize(array('sColor' => $this ->sColor, 'sText' => $this ->sText, 'sBorder' => $this ->sBorder));

return new memento($this ->sState);

}

public function restoreFromMemento($oMemento) {

if (!$oMemento instanceof memento) {

throw new Exception(__METHOD__.'() : parameter given must be a memento object');

}

$this ->sState = $oMemento ->getSavedState();

$aProps = unserialize($this ->sState);

foreach ($aProps as $sProp => $sVal) {

$this -> $sProp = $sVal;

}

echo 'Etat restauré via le memento<br />';

}

}


/**

* Le MEMENTO qui se charge de sauvegarder l'état et de le retourner si demandé

*

*/

class memento{

private $sState;


public function __construct($sState) {

$this ->sState = $sState;

}

public function getSavedState() {

return $this ->sState;

}

}


/**

* Le CARETAKER qui fait les demandes au MEMENTO

*

*/

class caretaker extends ArrayIterator {

public function addMemento($oMemento) {

$this ->append($oMemento);

return $this ->count() - 1;

}

public function getMemento($iOffset) {

if(!$this ->offsetExists($iOffset)) {

throw new Exception('No saved memento at position '.$iOffset);

}

return $this ->offsetGet($iOffset);

}

}


try {

$oCaretaker = new caretaker;

$oButton = new button;

$oButton ->COLOR = 'orange';

$oButton ->TEXT = 'Mon bouton';

echo $oButton;

$iSavedPosition1 = $oCaretaker ->addMemento($oButton ->saveToMemento());

$oButton ->COLOR = 'green';

$oButton ->BORDERCOLOR = 'blue';

echo $oButton;

$iSavedPosition2 = $oCaretaker ->addMemento($oButton ->saveToMemento());

$oButton ->COLOR = 'red';

$oButton ->BORDERCOLOR = 'gray';

echo $oButton;

$oButton ->restoreFromMemento($oCaretaker ->getMemento($iSavedPosition1));

echo $oButton;

} catch(Exception $e) {

echo $e;

}

?>



STATE

Le state (ou Etat) représente l'état d'un objet. Il permet d'agir en conséquence en fonction de l'état d'un objet, et donc de modifier son comportement. Les méthodes d'état auront des actions différentes en fonction de l'état courant de l'objet.

De plus, ce design pattern peut permettre d'appliquer à l'objet le fonctionnement d'un interrupteur : la méthode d'état X peut modifier l'objet vers l'état Y, et la methode d'état Y vers l'état X.

L'état est composé de :

- une classe abstraite d'état

- X classes concrêtes d'état

- la classe dont l'état change

Deux petits exemples ici. Le premier montre le fonctionnement du design pattern de manière simple, et le second montrera la notion "d'interrupteur".

Premier exemple :


<?php

/**

* STATE abstrait définissant les méthodes d'état

*

*/

abstract class abstractVendeur {

protected $sName;

public function __construct($sName) {

$this ->sName = $sName;

}

abstract public function welcome();

abstract public function advice();

abstract public function bye();

}


/**

* Un STATE concrêt

*

*/

class vendeurSympa extends abstractVendeur {

public function welcome() {

echo $this ->sName.' : Bonjour, que puis-je faire pour vous ?<br />';

}

public function advice() {

echo $this ->sName.' : Ah, je trouve que le bleu vous va beaucoup mieux!<br />';

}

public function bye() {

echo $this ->sName.' : Je vous en prie, bonne journée!<br />';

}

}


/**

* Un autre STATE concrêt

*

*/

class vendeurPasSympa extends abstractVendeur {

public function welcome() {

echo $this ->sName.' : Ouais, vous voulez quoi?<br />';

}

public function advice() {

echo $this ->sName.' : Chais pas, ce sont vos goûts.<br />';

}

public function bye() {

echo $this ->sName.' : C\'est ça.<br />';

}

}


/**

* L'objet dont l'état change

*

*/

class vendeur {

private $oEtat;

public function __construct() {

$this ->oEtat = new vendeurSympa(__CLASS__);

}

public function process() {

$this ->oEtat ->welcome();

$this ->oEtat ->advice();

$this ->oEtat ->bye();

}

public function setSympa() {

$this ->oEtat = new vendeurSympa(__CLASS__);

}

public function setPasSympa() {

$this ->oEtat = new vendeurPasSympa(__CLASS__);

}

}


/**

* On teste :-)

*/

$oVendeur = new vendeur;

$oVendeur ->process();

$oVendeur ->setPasSympa();

$oVendeur ->process();

?>

Second exemple montrant la notion de chengement d'état dynamique :


<?php

/**

* STATE abstrait : j'ai utilisé une interface cette fois car je n'avais rien à implémenter ici

*

*/

interface interrupteur {

public function switchSate();

}


/**

* STATE concrêt : interrupteur allumé, qui va aussi changer l'état de l'objet cible

*

*/

class interrupteurOn implements interrupteur {

public function switchSate() {

echo 'Je suis allumé<br />';

return new interrupteurOff;

}

}


/**

* STATE concrêt : interrupteur éteint, qui va aussi changer l'état de l'objet cible

*

*/

class interrupteurOff implements interrupteur {

public function switchSate() {

echo 'Je suis éteint<br />';

return new interrupteurOn;

}

}


/**

* Notre objet cible, le bouton

*

*/

class bouton {

private $oEtat;

public function __construct() {

$this ->oEtat = new interrupteurOn;

}

public function switchMe() {

$this ->oEtat = $this ->oEtat ->switchSate();

}

}


/**

* Et on teste!

*/

$oSwitch = new bouton;

$oSwitch ->switchMe();

$oSwitch ->switchMe();

$oSwitch ->switchMe();

$oSwitch ->switchMe();

$oSwitch ->switchMe();

$oSwitch ->switchMe();

?>



VISITOR

Le VISITOR (visiteur) autprise une classe à appeler un visiteur avec sa propre instance d'elle-même. Le visiteur possède des méthodes propres à chaque classe qu'elle peut visiter. Cela perlet de séparer un algorithme de la structure d'une classe qui en a besoin. Cela se passera par l'intermédiaire d'une méthode visitee::accept().

Il se compose de :

- un ABSTRACT VISITEE, classe d'abstraction pour les classes pouvant être visitée. Elle déclare une méthode accept()

- x CONCRETE VISITEE implémentant l'ABSTRACT VISITEE

- un ABSTRACT VISITOR qui va déclarer les méthodes de visites dont on a besoin

- x CONCRETE VISITOR dédiés à la visite des VISITEE

Dans le petit exemple, nous imaginons un catalogue composé de jeux vidéos et de films. Les attributs des films et des jeux sont différents, même si le tout se ressemble. UN jeu par exemple tourne sur une certaine plateforme...tandis qu'un film a un producteur.

Nous allons utiliser le VISITOR afin d'homogénéiser tout ça :


<?php

/**

* ABSTRACT VISITEE

*

*/

abstract class visitee {

abstract function accept(visitor $visitorIn);

}


/**

* CONCRETE VISITEE pour les jeux

*

*/

class gameVisitee extends visitee {

private $sType;

private $sPlatform;

private $sTitle;

public function __construct($sTitle, $sType, $sPlatform) {

$this->sType = $sType;

$this->sTitle=$sTitle;

$this->sPlatform=$sPlatform;

}

public function getType() {

return $this->sType;

}

public function getPlatform() {

return $this->sPlatform;

}

public function getTitle() {

return $this->sTitle;

}

public function accept(visitor $visitor) {

$visitor->visitGame($this);

}

}


/**

* CONCRETE VISITEE pour les films

*

*/

class movieVisitee extends visitee {

private $sCategory;

private $sProducer;

private $sTitle;

public function __construct($sTitle, $sCategory, $sProducer) {

$this->sCategory = $sCategory;

$this->sTitle=$sTitle;

$this->sProducer=$sProducer;

}

public function getCategory() {

return $this->sCategory;

}

public function getProducer() {

return $this->sProducer;

}

public function getTitle() {

return $this->sTitle;

}

public function accept(visitor $visitor) {

$visitor->visitMovie($this);

}

}


/**

* ABSTRACT VISITOR

*

*/

abstract class visitor {

abstract function visitGame(gameVisitee $gameVisitee);

abstract function visitMovie(movieVisitee $movieVisitee);

}


/**

* CONCRETE VISITOR renvoyant la description du VISITEE qu'il visite

*

*/

class catalogVistor extends visitor {

private $sDescription;

private function setDescr($sOutput) {

$this->sDescription = $sOutput;

}

public function getDescr() {

return $this->sDescription;

}

function visitGame(gameVisitee $gameVisitee) {

$sOutput = <<<eos

<p><strong>{$gameVisitee->getTitle()}</strong><br />

TYPE : <em>{$gameVisitee->getType()}</em><br />

PLATFORM: <em>{$gameVisitee->getPlatform()}</em></p>

eos;

$this->setDescr($sOutput);

}

function visitMovie(movieVisitee $movieVisitee) {

$sOutput = <<<eos

<p><strong>{$movieVisitee->getTitle()}</strong><br />

CATEGORY : <em>{$movieVisitee->getCategory()}</em><br />

PRODUCER: <em>{$movieVisitee->getProducer()}</em></p>

eos;

$this->setDescr($sOutput);

}

}


/**

* Et le test :-)

* A noter que je profite ici d'une possibilité de PHP5 : en PHP5, les objets sont toujours passés par référence!

* Ce qui fait qu'en donnant tour à tour le catalogVisitor au gameVisitee puis au movieVisitee, je change son état.

* Du coup, je peux ensuite appeler sa méthode getDescr() après chacune de ses visites.

*/

$visitor = new catalogVistor;


$game = new gameVisitee('My fancy RPG', 'RPG', 'PC');

$movie = new movieVisitee('My great movie', 'action', 'me myself and I');


$game->accept($visitor);

echo $visitor->getDescr();


$movie->accept($visitor);

echo $visitor->getDescr();

?>



CHAIN OF RESPONSIBILITY

Le CHAIN OF RESPONSIBILITY (chaîne de responsabilité) permet de se déplacer dans une chaîne d'objets jusqu'à en trouver un qui va accepter de prendre la responsabilité du traitement.

Ce design pattern se compose :

- de la classe à traiter

- d'un ABSTRACT HANDLER avec une méthode permettant de définir le prochain maillon de la chaîne

- de x CONCRETE HANDLER qui sont autant de maillons de la chaîne de responsabilité

Dans notre exemple, nous imaginons des joueurs qui ont fait un certain score dans un jeu.

Nous allons utiliser la chaîne pour leur donner un niveau en fonction de leur score :-)

On pourrait évidemment imaginer une classe intermédiaire qui va non seulement collecter nos joueurs, mais aussi les maillons de

la chaîne, puis lancer le traitement :


<?php

/**

* La classe principale sur laquelle nous allons appliquer notre chaine de responsabilités

*

*/

class gamer {

private $sName;

private $iPoints;

private $iLevel;

public function __construct($sName, $iPoints) {

if(!is_int($iPoints)) {

throw new Exception('Points must be an integer');

}

$this->sName=$sName;

$this->iPoints = $iPoints;

}

public function setLevel($iLevel) {

if (!is_int($iLevel)) {

throw new Exception('Level must be an integer');

}

$this->iLevel = $iLevel;

}

public function __get($sProp) {

switch($sProp) {

case 'NAME':

return $this->sname;

break;

case 'POINTS':

return $this->iPoints;

break;

case 'LEVEL':

if (is_null($this->iLevel)) {

throw new Exception('Level has not been set');

}

return $this->iLevel;

break;

default:

throw new Exception('Property '.$sProp.' cannot be get');

}

}

}


/**

* ABSTRACT HANDLER

*

*/

abstract class level extends SplObjectStorage {

protected $nextLevel;

public function setNext(level $nextLevel) {

$this->nextLevel = $nextLevel;

}

}


/**

* CONCRETE HANDLER qui va être le premier maillon de notre chaîne

*

*/

class level1 extends level {

public function process() {

foreach($this as $oGamer) {

if ($oGamer->POINTS <= 100) {

$oGamer->setLevel(1);

} elseif(!is_null($this->nextLevel)) {

$this->nextLevel->attach($oGamer);

$this->nextLevel->process();

} else {

throw new Exception('Next in the chain of command has not been set, cannot handle request');

}

}

}

}


/**

* CONCRETE HANDLER qui va être le second maillon de notre chaîne

*

*/

class level2 extends level {

public function process() {

foreach($this as $oGamer) {

if ($oGamer->POINTS <= 200) {

$oGamer->setLevel(2);

} else {

$this->nextLevel->attach($oGamer);

$this->nextLevel->process();

}

}

}

}


/**

* CONCRETE HANDLER qui va être le troisième et dernier maillon de notre chaîne

*

*/

class level3 extends level {

public function process() {

foreach($this as $oGamer) {

if ($oGamer->POINTS <= 300) {

$oGamer->setLevel(3);

} else {

$this->nextLevel->attach($oGamer);

$this->nextLevel->process();

}

}

}

}


/**

* Notre petit test :-)

*/

$neo = new gamer('neo', 99);

$dante = new gamer('dante', 150);

$jeremy = new gamer('jeremy', 225);


$level1 = new level1;

$level2 = new level2;

$level3 = new level3;

$level1->setNext($level2);

$level2->setNext($level3);

$level1->attach($neo);

$level1->attach($dante);

$level1->attach($jeremy);

$level1->process();


echo 'Niveau atteint par Neo : '.$neo->LEVEL;

echo 'Niveau atteint par Dante : '.$dante->LEVEL;

echo 'Niveau atteint par Jeremy : '.$jeremy->LEVEL;

?>



COMMAND

Le COMMAND (commande) permet de séparer le code initiateur d'une action, de l'action elle-même.

La commande va se charger de communiquer l'action à effecuer, avec les paramètres requis par cette dernière.

Notre petit exemple va nous faire construire une balise "span" personnalisée :


<?php

/**

* RECEIVER, la classe sur laquelle on applique le COMMAND

*

*/

class span {

private $sContents;

private $aStyles = array();

public function __construct($sContents) {

$this->sContents = $sContents;

}

public function getSpan() {

$sStyle = $this->getStyles();

return '<span '.$sStyle.'>'.$this->sContents.'</span>';

}

private function getStyles() {

return 'style="'.implode(';', $this->aStyles).';"';

}

public function setFontColor($sColor) {

$this->aStyles[]='color:'.$sColor;

}

public function setBackgroundColor($sColor) {

$this->aStyles[]='background-color:'.$sColor;

}

public function setBorders($iSize, $sStyle, $sColor) {

$this->aStyles[]='border:'.$iSize.'px '.$sStyle.' '.$sColor;

}

}


/**

* ABSTRACT COMMAND qui prend le RECEIVER en paramètre et déclare la méthode execute()

*

*/

abstract class command {

protected $receiver;

public function __construct(span $span) {

$this->receiver = $span;

}

abstract public function execute();

}


/**

* Une CONCRETE COMMANDE

*

*/

class commandFontColorRed extends command {

public function execute() {

$this->receiver->setFontColor('red');

}

}


/**

* Une autre CONCRETE COMMANDE

*

*/

class commandFontColorblue extends command {

public function execute() {

$this->receiver->setFontColor('blue');

}

}


/**

* ...etc

*

*/

class commandBackgroundColorOrange extends command {

public function execute() {

$this->receiver->setBackgroundColor('orange');

}

}


class commandBackgroundColorGreen extends command {

public function execute() {

$this->receiver->setBackgroundColor('green');

}

}


class commandBorderSolid extends command {

public function execute() {

$this->receiver->setBorders(1, 'solid', 'black');

}

}


class commandBorderDottedDouble extends command {

public function execute() {

$this->receiver->setBorders(2, 'dotted', 'black');

}

}


/**

* Notre test :-)

*/

$span = new span('hello');

echo $span->getSpan();

$command = new commandFontColorblue($span);

$command->execute();

echo $span->getSpan();

$command = new commandBackgroundColorOrange($span);

$command->execute();

echo $span->getSpan();

$command = new commandBorderDottedDouble($span);

$command->execute();

echo $span->getSpan();

?>



MEDIATOR

Le MEDIATOR (médiateur) définit un objet qui intègre les interactions d'une collection d'objets (COLLEAGUES).

Le médiateur connait tous les objets de sa collection, et chaque objet COLLEAGUE connait uniquement son médiateur.

Dans notre exemple, nous allons simuler un combat entre le héro et le méchant.

A noter que l'on pourrait s'amuser avec l'API de Réflexion pour optimiser et améliorer tout ça : http://www.php.net/manual/fr/language.oop5.reflection.php

Le héro et le méchant ont chacun leur familier. Ce qui arrive à l'un arrive à l'autre. Tous peuvent attaquer, et soigner :


<?php

/**

* ABSTRACT MEDIATOR

*

*/

abstract class abstractMediator {

abstract public function hit(character $giver, character $receiver, $iPoints);

abstract public function heal(character $giver, character $receiver, $iPoints);

}


/**

* CONCRETE MEDIATOR

*

*/

class mediator extends abstractMediator {

private $hero;

private $minion;

private $villain;

private $pet;


public function setHero(hero $hero) {

$this->hero = $hero;

}

public function setPet(pet $pet) {

$this->pet = $pet;

}

public function setMinion(minion $minion) {

$this->minion = $minion;

}

public function setVillain(villain $villain) {

$this->villain = $villain;

}

public function hit(character $giver, character $receiver, $iPoints) {

$sReceiver = get_class($receiver);

$sGiver = get_class($giver);

$this->$sReceiver->setPoints(-$iPoints);

$this->$sReceiver->notify('got hit by '.$sGiver.' : '.$iPoints);

if ($receiver instanceof hero) {

$this->pet->setPoints(-$iPoints);

$this->pet->notify('lost : '.$iPoints);

} elseif($receiver instanceof pet) {

$this->hero->setPoints(-$iPoints);

$this->hero->notify('lost : '.$iPoints);

} elseif($receiver instanceof villain) {

$this->minion->setPoints(-$iPoints);

$this->minion->notify('lost : '.$iPoints);

} else {

$this->villain->setPoints($iPoints);

$this->villain->notify('lost : '.$iPoints);

}

}

public function heal(character $giver, character $receiver, $iPoints) {

$sReceiver = get_class($receiver);

$sGiver = get_class($giver);

$this->$sReceiver->setPoints($iPoints);

$this->$sGiver->notify('healed '.$sReceiver.' : '.$iPoints);

if ($receiver instanceof hero) {

$this->pet->setPoints($iPoints);

$this->pet->notify('gained : '.$iPoints);

} elseif($receiver instanceof pet) {

$this->hero->setPoints($iPoints);

$this->hero->notify('gained : '.$iPoints);

} elseif($receiver instanceof villain) {

$this->minion->setPoints($iPoints);

$this->minion->notify('gained : '.$iPoints);

} else {

$this->villain->setPoints($iPoints);

$this->villain->notify('gained : '.$iPoints);

}

}

}


/**

* ABSTRACT COLLEAGUE

*

*/

abstract class character {

private $mediator;

protected $iLife;

public function __construct(mediator $mediator) {

$this->mediator=$mediator;

}

public function hit(character $char, $iPoints) {

$this->mediator->hit($this, $char, $iPoints);

}

public function heal(character $char, $iPoints) {

$this->mediator->heal($this, $char, $iPoints);

}

public function setPoints($iPoints) {

$this ->iLife += $iPoints;

}

public function notify($sMsg) {

echo get_class($this).' '.$sMsg;

}

public function getPoints() {

return $this->iLife;

}

}


/**

* CONCRETE COLLEAGUE HERO

*

*/

class hero extends character {

protected $iLife = 100;

}



/**

* CONCRETE COLLEAGUE PET

*

*/

class pet extends character {

protected $iLife = 100;

}


/**

* CONCRETE COLLEAGUE MINION

*

*/

class minion extends character {

protected $iLife = 50;

}


/**

* CONCRETE COLLEAGUE VILLAIN

*

*/

class villain extends character {

protected $iLife = 200;

}


/**

* Commençons le combat!

*/


$mediator = new mediator;


$hero = new hero($mediator);

$pet = new pet($mediator);

$minion = new minion($mediator);

$villain = new villain($mediator);


$mediator->setHero($hero);

$mediator->setPet($pet);

$mediator->setMinion($minion);

$mediator->setVillain($villain);


$villain->hit($hero, 50);

$hero->heal($hero, 20);

$hero->hit($villain, 30);


echo $hero->getPoints();

echo $pet->getPoints();

echo $villain->getPoints();

echo $minion->getPoints();

?>



INTERPRETER

Un INTERPRETER définit la grammaire d'un langage spécialisé dont on a besoin. Il l'interprête afin de le traduire.

Un exemple sera plus parlant. Les plus vieux d'entre vous (dont je fais partie) se souviendront sans doute d'un langage nommé LOGO.

Pour les autres, ce langage permettait de dessiner sur l'écran, grâce à des commandes assez basiques. Cela déplaçait une tortur sur l'écran...très fun ;-)

On reprend exactement le même principe ici!

Notre langage de dessin accepte 4 instructions :

right

left

up

down

L'API de Réflexion est utilisée ici pour invoquer les méthodes statiques des classes EXPRESSION.

Plus de détails sur cet API ici : http://www.php.net/manual/fr/language.oop5.reflection.php

Chaque instruction doit être suivie d'un entier représentant la distance à parcourir sur le dessin dans la direction souhaitée. On pourrait très facilement aller plus loin avec cette classe, ce serait d'ailleurs un bon exercice. Testez-là, c'est très amusant, vous verrez :-) Vous pouvez ajouter la possibilité d'initialiser et réinitialiser le départ, paramètrer la taille de l'image, la couleur du trait, son épaisseur, ajouter des commandes etc... (à noter que cet exemple requiert GD2 pour fonctionner, hein) :


<?php

/**

* Notre INTERFACE EXPRESSION

*

*/

interface expression {

public static function interpret($iDistance);

}


/**

* Une EXPRESSION CONCRETE pour les mouvements vers la droite

*

*/

class right implements expression {

public static function interpret($iDistance) {

return array(0, 0, $iDistance, 0);

}

}


/**

* Une EXPRESSION CONCRETE pour les mouvements vers la gauche

*

*/

class left implements expression {

public static function interpret($iDistance) {

return array(0, 0, -$iDistance, 0);

}

}


/**

* Une EXPRESSION CONCRETE pour les mouvements vers le haut

*

*/

class up implements expression {

public static function interpret($iDistance) {

return array(0, 0, 0, -$iDistance);

}

}


/**

* Une EXPRESSION CONCRETE pour les mouvements vers le bas

*

*/

class down implements expression {

public static function interpret($iDistance) {

return array(0, 0, 0, $iDistance);

}

}


/**

* Notre INTERPRETER

*

*/

class interpreter {

private $iX = 200;

private $iY = 200;

private $aDefaultDim = array(500,500);

private $oExpressionStack;

private $oTranslationStack;

private $imh;

public function __construct() {

$this->oTranslationStack = new ArrayIterator;

}

public function interpret ($sChaine) {

if(!is_string($sChaine)) {

throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : Parameter must be a string');

}

$this->oExpressionStack = new ArrayIterator(explode(' ', $sChaine));

$this->evaluate();

$this->buildMove();

}

private function evaluate() {

while($this->oExpressionStack->valid()) {

$sToken = $this->oExpressionStack->current();

if(!class_exists($sToken)) {

throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : invalid expression '.$sToken);

}

$this->oExpressionStack->next();

$iDistance = $this->oExpressionStack->current();

if(!is_numeric($iDistance)) {

throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : invalid distance '.$iDistance);

}

$interpreter = new ReflectionMethod($sToken, 'interpret');

$this->oTranslationStack[] = $interpreter->invoke(null, (int)$iDistance);

$this->oExpressionStack->next();

}

}

private function buildMove() {

$this->imh = imagecreatetruecolor($this->aDefaultDim[0], $this->aDefaultDim[1]);

$bgh = imagecolorallocate($this->imh, 0, 0, 0);

$colh = imagecolorallocate($this->imh, 255, 255, 255);

foreach($this->oTranslationStack as $aMove) {

imageline($this->imh, $this->iX+$aMove[0], $this->iY+$aMove[1], $this->iX+$aMove[2], $this->iY+$aMove[3], $colh);

$this->iX += $aMove[2];

$this->iY += $aMove[3];

}

}

public function getMove() {

if(true === headers_sent()) {

throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : Unable to display image, headers already sent');

}

header('Content-type: image/png');

imagepng($this->imh);

}

}

/**

* Notre test! Nous allong afficher un joli dessin à la façon du vieux langage dinosaure LOGO

*/

try {

$oInterpreter = new interpreter;

$oInterpreter->interpret('up 100 right 50 down 100 left 10 up 90 left 30 down 90 left 10 right 10 up 50 right 30 up 5 left 30');

$oInterpreter->getMove();

} catch(Exception $e) {

echo $e;

}

?>


11 octobre 2007 18:24:11 :
Problème de mise en forme qui a viré tout un tas de fonctions des codes exemples :-( J'espère ne rien avoir oublié.
11 octobre 2007 18:34:16 :
Re changement, tjrs le même problème...grrr
signaler à un administrateur
Commentaire de coucou747 le 11/10/2007 20:45:14

pour INTERPRETER, je suis en fait etonne... pour moi, le shemat classique pour ce genre de problemes (enfin pour des syntaxes bien plus compliquees) c'est le parseur lexeur et les nodex (tokens et formes reduites)
Exemple : mon parseur de CSS (http://www.phpcs.com/codes/PILE-PARSEUR-LEXER-TOKENS_44089.aspx)

signaler à un administrateur
Commentaire de malalam le 11/10/2007 21:05:31 administrateur CS

L'interpreter dans sa forme standard est composé d'une classe expression abstraite, et de classes abstractions concrêtes (voire finales).
A côté de cela, on a le "contexte" (la classe interpreter) qui se charge de l'input et de l'output. On peut avoir aussi recours à une classe "client" évidemment, afin d'agencer tout ça plus simplement.
Le tout en fait un design proche d'un composite. En fait, c'est un composite spécialisé.
Un parseur, c'est différent; on n'utilisera pas l'interpreter pour des grammaires trop complexes, et dans ce cas, justement, on utilisera un parseur, avec ce que tu y suggères.
Comme tu le dis : une approche est là pour les grammaires simples (l'interpreter) et une autre pour les grammaires beaucoup plus complexes (le parseur avec son lexer et ses tokens), mais évidemment, suivant peu ou prou le même schéma. Simplement, avbec une grammaire complexe, on doit décomposer! Ici, je n'ai pas besoin de réellement décomposer, la grammaire est simple et je travaille sur des chaînes courtes suivant toujours le même processus basique.
C'est la base de l'interpreter.
Mais je n'ai jamais dit qu'on ne devait pas aller plus loin si l'on voulait réaliser un parseur complexe. D'ailleurs, on a ici un peu du composite, mais en poussant, on pourrait très bien avoir à se servir de bcp plus de design patterns pour créer un interpreter puissant. Et le lexer -> tokens -> parser est un ensemble de design patterns, une apoplication bein plus poussée.

signaler à un administrateur
Commentaire de miss fafa le 16/10/2007 00:21:48

slt tt le monde,
beh rien comprie ,svp essayer d'expliquer un peu pour ca soit clair;
merci en avance

signaler à un administrateur
Commentaire de malalam le 16/10/2007 00:38:38 administrateur CS

Heu...qu'est-ce que tu n'as pas compris, Miss Fafa ?

signaler à un administrateur
Commentaire de codefalse le 19/10/2007 11:14:17 administrateur CS

il manque quand meme un truc énorme par rapport aux autres tutos Malamam : la couleur du code, et l'identation, afin de mieux se reperer :/

Sinon, niveau qualité, rien à redire, c'est du tres tres bon, comme d'hab :)

signaler à un administrateur
Commentaire de malalam le 19/10/2007 11:48:38 administrateur CS

Merci :-)

Pour la mise en page, soit on la fait à la mano (burk), soit on utilise docx. Je n'ai pas Office chez moi, j'ai donc utilisé OpenOffice avec un addon docx. Mais CS n'a pas aimé, et Word au bureau non plus à dire vrai...j'ai donc transformé en doc, ouvert sous Word au bureau, transformé en docx...et à l'importation il m'a viré toutes les fonctions soulignées par la colorieur de code Geshi.
Du coup j'ai abandonné... ;-)
Mais je referai ça proprement plus tard :-)

signaler à un administrateur
Commentaire de FhX le 30/10/2007 23:21:17

Oh oui, magnifique :)

Je ne passe plus très souvent, faut m'excuser... mais je suis passé sur Java pour un ptit moment (même si je fais du PHP à coté =)).


J'avoue que les designs patterns sont les mêmes, donc on ne s'y perd pas vraiment :)
Les observers, par exemple, fonctionnent de la même manière (bien que... ^^)

Cela étant, bravo pour les tutos.
Un grand 10/10 pour ma part :)

Le petit truc qui m'embète (je n'ai lu que des ptits bouts, je suis tombé la dessus), c'est ca :
- un ABSTRACT VISITEE, classe d'abstraction pour les classes pouvant être visitée. Elle déclare une méthode accept()

J'avoue ne pas comprendre le concept mère/fille ici. Une visite en elle-même n'est pas un objet dans ce cas précis, mais au mieux une interface.
Car rien n'empèche de faire ca :

interface iMyInterface {
public function MyMethod();
}

class x implements iMyInterface {
public function MyMethod();
}

class truc {
public function getClassWithMyInterface( iMyInterface $class );
}


Il me semble que cela est pareil pour tout le document.
Mais ce n'est qu'une erreur minime qui n'entache en rien les explications des designs pattern :)
Simplement un point de vue OO :D

héhé =)

signaler à un administrateur
Commentaire de malalam le 03/11/2007 12:24:53 administrateur CS

Hello Fhx,

tout d'abord, ça fait vraiment plaisir de te revoir un peu :-)
Java a copié sur PHP, c'est pour ça que tu t'y retrouves bien...Ah non, c'est peut-être le contraire ;-)

Pour cette série de tuto, pour être franc, je n'ai vraiment approfondi au niveau du code que les design patterns qui m'intéressaient vraiment; le VISITOR n'en faisait pas partie.

Et effectivement, après relecture du code, une interface suffit amplement à la tâche. C'est une erreur de ma part, que je corrigerai ce we. Je ne suis juste pas allé cherché trop loin sur ce code, je l'ai juste traduit :-) Bon à ma décharge, ça a demandé pas mal de recherches, des tutos lol, et j'avoue être parfois passé assez vite sur certains trucs qui m'intéressaient moyennement.
Globalement, le VISITOR est une structure très classique. Trop sans doute. Si on voulait l'exploiter réellement, il faudrait un exemple bien plus poussé que celui-ci, qui se contente d'implémenter des getters, et ne montre pas l'exhaustivité de ses possibilités.

signaler à un administrateur
Commentaire de littlewings le 21/11/2007 10:40:56

Malalam, 1000 merci pour ces tutos sur les design pattern en PHP5 !!! C'est vraiment trop de la balle ! Bon, j'ai pas tout capté (surtout dans ce dernier cours), mais c'est vraiment clair et je bookmarque tout cela : c'est en forgeant qu'on devient forgeron et j'imagine qu'un jour ou l'autre je serais emmener à comprendre et utiliser !

10/10 pour tes tutos, encore bravo et merci !

signaler à un administrateur
Commentaire de malalam le 21/11/2007 11:52:11 administrateur CS

Merci à toi :-) Et très heureux que mes tutos te plaisent et surtout, te soient utile !

signaler à un administrateur
Commentaire de stay le 28/12/2007 16:49:12

J'apprécie ce geste tu donnes une vision professionnelles au codage PHP5 POO de plus à ce jour je ne crois pas avoir vu une documentation ou un livre expliquant le design pattern aussi avancer et en français.

Un grand merci 10/10.

signaler à un administrateur
Commentaire de malalam le 28/12/2007 18:52:19 administrateur CS

Merci Stay :-)
Moi non plus, pas en PHP5 en tout cas, je n'ai rien vu de bien folichon, d'où l'idée de ces tutos.
Ravi que cela te plaise, ça fait plaisir :-)

signaler à un administrateur
Commentaire de FhX le 03/01/2008 15:31:57

Je viens de relire ca :
private function evaluate() {

while($this->oExpressionStack->valid()) {

$sToken = $this->oExpressionStack->current();

if(!class_exists($sToken)) {

throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : invalid expression '.$sToken);

}

$this->oExpressionStack->next();

$iDistance = $this->oExpressionStack->current();

if(!is_numeric($iDistance)) {

throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : invalid distance '.$iDistance);

}

$interpreter = new ReflectionMethod($sToken, 'interpret');

$this->oTranslationStack[] = $interpreter->invoke(null, (int)$iDistance);

$this->oExpressionStack->next();

}

}

Et pis j'me suis dit, nan, c'est pas possible Mala a OBLIGATOIREMENT fait un truc qui fallait pas :p
Et oui oui, j'ai trouvé la ptite bête :D :D

Voici la version corrigé (ceci dit, l'interpréteur n'est pas correct car tu tests obligatoirement un INT a la suite d'une commande. Il faudrait en réalité parser les arguments entre 2 commandes, mais bon, la n'est pas le sujet :

private function evaluate() {
   while($this->oExpressionStack->valid()) {
         $sToken = $this->oExpressionStack->current();
          if( ! ($sToken instanceof expression )
                throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : invalid expression '.$sToken);

         // Ici, il faudrait un parsing un peu plus poussé :D
         $this->oExpressionStack->next();
         $aArgs = $this->oExpressionStack->current();
          if( !is_numeric($iDistance) )
               throw new Exception(__CLASS__.'::'.__FUNCTION__.'() : invalid distance '.$iDistance);
        
         // On casse la reflection inutile en PHP, chose OBLIGATOIRE en Java par contre :)
         $interpreter = new $sToken();
         $this->oTranslationStack[] = $interpreter->interpret($aArgs);
         $this->oExpressionStack->next();
  }

}

Bon à savoir, cette astuce permet par exemple l'utilisation de module avec argument à la queue.
En clair, rappelez vous les t'chat IRC :
/nickname mon_nom
/me kikoo
/to moi hello
/dccsend monfichier.xxl
/uptime

etc...
Eh bien, il suffit de parser la chaine et de vérifier que le module correspondant à la commande est chargé en mémoire pour que tout fonctionne automatiquement.

'Même moi je m'en sers :) D'une autre facon cependant !

Hop hop hop, j'ai fini de faire chier mon monde :D :D

signaler à un administrateur
Commentaire de malalam le 04/01/2008 19:06:44 administrateur CS

Et hello encore FhX :-)

Y a deux choses : les méthodes interpret() sont statiques.
Et si je suis passé par la réflexion, je crois que c'est poarce que je ne suis pas parvenu à appeler ma méthode statique autrement.
Mais j'avoue qu'il faut que je reteste...mon nouveau code est basé sur cet interprêteur (photophop), je testerai.
Pour le is_numeric, c'est surtout par flemme, pour être franc...dans photophop de toute manière je passe par une expression régulière parce que je n'ai plus forcément des entiers.

Je vais jeter un oeil ce we et mettrai mes résultats ici :-)

Ajouter un commentaire



Nos sponsors

Sondage...

CalendriCode

Juillet 2009
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
2728293031  

Consulter la suite du CalendriCode

Comparez les prix Nouvelle version


HTC Magic

Entre 429€ et 429€


Photothèque Nouveau !



Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés
Temps d'éxécution de la page : 0,218 sec

Google Coop CodeS-SourceS Google Coop CodeS-SourceS


Certaines images présentes sur le site (notament certains avatars) sont issues des collections IconShock, donc si vous souhaitez utiliser ces icons vous devez les acheter, ne les copiez pas et ne utilisez pas dans vos sites et applications sans les avoir commandé.