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 [2]


Information sur le tutorial

Catégorie :Class et Objet ( POO ) Date de création : 31/08/2007 18:57:29 Vu : 7 323 fois

Note :
Aucune note

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

Description

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 ! ;-)

Voici la suite de cette trilogie (au moins) : la partie 2

Tutorial

DESIGN PATTERNS (ou MOTIFS DE CONCEPTION). PART 2


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 donc

ré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. Ce tuto va s'occuper des design patterns de type STRUCTUREL.



--STRUCTUREL--



COMPOSITE

Le composite permet de manipuler un groupe d'objets au fonctionnement similaire comme s'il s'agissait d'un seul objet.

Il comprend :

Le composant, une interface abstraite définissant le comportement, et déclare l'accès aux composants enfants (feuilles)

Le composite : stocke les feuilles et implémente leur gestion

La feuille : objet de la composition

L'exemple que je vais vous soumettre est une simulation d'application graphique : nous allons composer un graphique fait de plusieurs

éléments graphiques, puis nous allons tous les dessiner en un seul appel.

Cet exemple utilise une classe interne PHP5 de la SPL : SplObjectStorage, objet très pratique, et je profite donc de l'occasion pour vous le présenter

en action, même s'il n'est pas nécessaire au motif (mais son fonctionnement l'est, par contre).

Pour rappel, voici la doc de la SPL : http://www.php.net/~helly/php/ext/spl/


<?php

/**

* On redéfinit la classe SplObjectStorage au cas elle n'existe pas, et SURTOUT pour que vous voyiez son fonctionnement.

*/

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;

                    }

                }

            }

        }

    }

}

/**

* Notre interface : le COMPOSANT

*

*/

interface graphic {

    public function draw();

}


/**

* Notre COMPOSITE!

*

*/

class compositeGraphic implements graphic {

    private $aGraphics;

    

    public function __construct() {

        $this ->aGraphics = new SplObjectStorage;

    }

    

    public function draw() {

        foreach ($this ->aGraphics as $oGraph) {

            $oGraph ->draw();

        }

    }

    

    public function attach(graphic $oGraph) {

        $this ->aGraphics ->attach($oGraph);

    }


    public function detach(graphic $oGraph) {

        $this ->aGraphics ->detach($oGraph);

    }

}


/**

* Notre FEUILLE

*

*/

class ellipse implements graphic {

    public function draw() {

        echo 'Ellipse'."\r\n";

    }

}


/**

* Une autre FEUILLE

*

*/

class triangle implements graphic {

    public function draw() {

        echo 'Triangle'."\r\n";

    }

}


try {

    /**

     * On crée plusieurs ellipses et triangles

     */

    $ellipse1 = new ellipse;

    $ellipse2 = new ellipse;

    $ellipse3 = new ellipse;

    

    $triangle1 = new triangle;

    $triangle2 = new triangle;    

    /**

     * On crée plusieurs compositeGraphic

     */

    $graf = new compositeGraphic;

    $graf1 = new compositeGraphic;

    $graf2 = new compositeGraphic;

    

    /**

     * On attache 2 ellipses et 1 triangle au compositeGraphic 1

     */

    $graf1 ->attach($ellipse1);

    $graf1 ->attach($ellipse2);

    $graf1 ->attach($triangle1);

    

    /**

     * On attache la 3ème ellipse et le deernier triangle au compositeGraphic 2

     */

    $graf2 ->attach($ellipse3);

    $graf2 ->attach($triangle2);

    

    /**

     * On attache les 2 compositeGraphic précédents au 3ème

     */

    $graf ->attach($graf1);

    $graf ->attach($graf2);

    

    /**

     * On trace notre grahique complet !

     */

    $graf ->draw();

} catch (Exception $e) {

    echo $e;

}

?>



PROXY

Le proxy se substitue à une autre classe. Il implémente la même interface (et une seule interface) que cette classe.

Il contrôle l'accès aux méthodes de la classe substituée. Il peut aussi être utilisée pour simplifier ou optimiser l'utilisation

de la classe substituée.

Cette fois, 2 exemples. Le premier pour illustrer le contrôle d'accès, le second pour illustrer l'optimisation :

Le 1er exemple, donc, simule l'accès à un compte utilisateur. La classe user possède un numéro de compte auquel on ne doit

pas pouvori accéder sans le mot de passe adéquat. Le proxy vérifie que cette condition soit remplie, et autorise l'accès au

numéro de compte uniquement dans ce cas-là :


<?php

/**

* L'interface

*

*/

interface iUser {

    public function getUserAccount();

}


/**

* La classe user qui va être substituée

* Possède un numéro de compte auquel on ne doit pas accéder sans le mot de passe!

*

*/

class user implements iUser {

    private $sAccount = '12345';

    

    public function __construct() {

        echo 'User initialisé<br />';

    }

    

    public function getUserAccount() {

        return $this ->sAccount;

    }

}


/**

* Le proxy se substituant à user.

* Le proxy va vérifier la validité du mot de passe avant de retourner le numéro de compte de l'utilisateur

*

*/

class userProxyProtection implements iUser {

    private $sPassword;

    private $oUser;

    

    public function __construct($sPwd) {

        $this ->sPassword = $sPwd;

        $this ->oUser = new user();

    }

    

    public function getUserAccount() {

        /**

         * Par commodité, je fixe ici le mot de passe demandé. En réalité, on passerait par une interrogation d'une base de données par exemple

         */

        if ($this ->sPassword !== 'motdepasse') {

            throw new Exception ('Mot de passe invalide');

        }

        return $this ->oUser ->getUserAccount();

    }

}


try {

    $oUser = new userProxyProtection('motdepasse');

    echo $oUser ->getUserAccount();

} catch (Exception $e) {

    echo $e;

}

?>


Le 2d exemple simule l'accès à de gros fichiers. On veut afficher leur contenu; il faut donc les charger.

Le proxy va s'occuper de s'assurer que l'on ne charge les fichiers que si c'est nécessaire (si on ne l'a pas déjà chargé et

uniquement si on veut l'afficher):


<?php

/**

* L'interface commune

*

*/

interface iBigFile {

    public function displayFile();

}


/**

* La classe bigFile qui charge et affiche un gros fichier

*

*/

class bigFile implements iBigFile {

    private $sFileName;

    

    public function __construct($sFileName) {

        $this ->sFileName = $sFileName;

        echo 'Charge ',$this ->sFileName, '<br />';

    }

    

    public function displayFile() {

        echo 'Affiche ', $this ->sFileName, '<br />';

    }

}


/**

* Le proxy qui va se substituer à bigFile et ne permettre le chargement d'un fichier QUE si c'est nécessaire (s'il n'a pas été déjà chargé et si l'on demande son affichage)

*

*/

class proxyBigFile implements iBigFile {

    private $sFileName;

    private $oFile;

    

    public function __construct($sFileName) {

        $this ->sFileName = $sFileName;

    }

    

    public function displayFile() {

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

            $this ->oFile = new bigFile($this ->sFileName);

        }

        $this ->oFile ->displayFile();

    }

}


$aFiles = array (new proxyBigFile('fichier1'), new proxyBigFile('fichier2'), new proxyBigFile('fichier3'));

$aFiles[0] ->displayFile();

$aFiles[1] ->displayFile();

$aFiles[0] ->displayFile(); // ce fichier (fichier1) ne sera pas rechargé puisqu'il l'a déjà été!

// A noter que le fichier3 n'a pas du tout été chargé puisqu'on ne demande pas son affichage!

?>



DELEGATION

Le Delegation est une technique avec laquelle un objet possède un comportement mais délègue la responsabilité de

l'implémentation de ce comportement à un autre objet.

A noter que cela résulte souvent en une perte de performances, là où l'on gagne en lisibilité.

Ce design pattern est très utile dans les cas où l'on a une multitude de classe héritées dont les comportements semblent

très proches mais sont en réalité assez différents : il en ressort alors avec une programmation classique des classes très

complexes héritant de-ci de-là et devenant incompréhensibles.

Voici un exemple simple simulant le comportement des hommes et des femmes :


<?php

/**

* Une interface pour décrire les comportements de notre package de classes

*

*/

interface iPeople {

    public function sayName();

    public function sayHello();

    public function drink();

    public function watchTv();

}


/**

* Une classe abstraite parente

* Cette classe va aller chercher les comportements de ses enfants

* Elle délègue...

*

*/

abstract class people implements iPeople {

    protected $sName;

    protected $drinkBehaviour;

    protected $watchTvBehaviour;

    

    public function __construct($sName) {

        $this ->sName = $sName;

    }

    

    public function sayName() {

        echo 'Mon nom est ', $this ->sName,'<br />';

    }

    

    public function sayHello() {

        echo 'Hello !<br />';

    }

    

    public function drink() {

        $this ->drinkBehaviour ->drink();

    }

    

    public function watchTv() {

        $this ->watchTvBehaviour ->watchTv();

    }


    public function setSayNameBehaviour($sayNameBehaviour) {

        $this ->sayNameBehaviour = $sayNameBehaviour;

    }

    

    public function setSayHelloBehaviour($sayHelloBehaviour) {

        $this ->sayHelloBehaviour = $sayHelloBehaviour;

    }

    

    public function setDrinkBehaviour($drinkBehaviour) {

        $this ->drinkBehaviour = $drinkBehaviour;

    }

    

    public function setWatchTvBehaviour($watchTvBehaviour) {

        $this ->watchTvBehaviour = $watchTvBehaviour;

    }

}


/**

* Une interface déclarant un comportement

*

*/

interface drinkBehaviour {

    public function drink();

}


/**

* Un comportement associé à une méthode drink()

* C'est un delegué

*

*/

class drinkBeer implements drinkBehaviour {

    public function drink() {

        echo 'Je bois de la bière'.'<br />';

    }

}


/**

* Un autre comportement, différent de celui ci-dessus, associé aussi à la méthode drink(), destiné à une autre classe

* C'est aussi un delegué

*/

class drinkCosmopolitan implements drinkBehaviour {

    public function drink() {

        echo 'Je bois des Cosmopolitains'.'<br />';

    }

}


/**

* Etc...

*

*/

interface watchTvBehaviour {

    public function watchTv();

}


class watchFootball implements watchTvBehaviour {

    public function watchTv() {

        echo 'Je regarde du foot à la télévision!'.'<br />';

    }

}


class watchDesperateHousewives implements watchTvBehaviour {

    public function watchTv() {

        echo 'Je regarde desperate Housewives à la télévision!'.'<br />';

    }

}


class man extends people implements iPeople {

    public function __construct($sName) {

        parent::__construct($sName);

        $this ->setDrinkBehaviour(new drinkBeer);

        $this ->setWatchTvBehaviour(new watchFootball);

    }

}


class woman extends people implements iPeople {

    public function __construct($sName) {

        parent::__construct($sName);

        $this ->setDrinkBehaviour(new drinkCosmopolitan);

        $this ->setWatchTvBehaviour(new watchDesperateHousewives);

    }

}


/**

* On teste les comportements :-)

*/

$oMan = new man('Paul');

$oWoman = new woman('Ingrid');

$oMan ->sayName();

$oWoman ->sayName();

$oMan ->sayHello();

$oWoman ->sayHello();

$oMan ->drink();

$oWoman ->drink();

$oMan ->watchTv();

$oWoman ->watchTv();

?>



FLYWEIGHT

Le flyweight (ou poids mouche) sert à limiter les ressources prises par une application demandant beaucoup de petites instances.

Il va se charger de sélectionner des objets déjà instanciés pouvant être réutilisés. Et avec une bonne méthodologie,

on pourra tout de même obtenir avec un même objet, des comportements différents (en déportant des propriétés de l'objet

vers des arguments de méthodes).

Petit exemple d'affichage html d'un texte :


<?php

/**

* Notre petite classe destinée à être instanciées de multiples fois sans l'intervention du flyweight

*

*/

class char {

    private $sChar;

    private $sFont;

    

    public function __construct($sChar, $sFont) {

        $this ->sChar = $sChar;

        $this ->sFont = $sFont;

    }

    

    public function printChar($iId) {

        echo '<span style="font-family:'.$this ->sFont.'" id="'.$iId.'">'.$this ->sChar.'</span>';

    }

}


/**

* Notre flyweight. Il stocke des char et ne va les instancier que s'il n'en a pas déjà stocké un identique

*

*/

class charFactory extends ArrayIterator {

    

    public function get($sChar, $sFont) {

        $sKey = $sChar.$sFont;

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

            $this ->offsetSet($sKey, new char($sChar, $sFont));

        }

        return $this[$sKey];

    }

}

/**

* On teste

*/

$oText = new charFactory;

$aTab = array();

$aTab[]=$oText ->get('H', 'Arial');

$aTab[]=$oText ->get('E', 'Arial');

$aTab[]=$oText ->get('L', 'Verdana');

$aTab[]=$oText ->get('L', 'Verdana');

$aTab[]=$oText ->get('O', 'Arial');

$iCpt = 1;

foreach ($aTab as $oChar) {

    $oChar ->printChar('_'.$iCpt);

    $iCpt ++;

}

/**

* Ici, on se rend compte qu'on aurait instancié 5 objets si l'on n'avait pas utilisé le flyweight (1 pour chaque lettre du mot HELLO).

* Le flyweight n'en a instancié que 4, puisque les 2 L sont identiques (L VERDANA) : il a simplement réutilisé le même objet.

* Néanmoins, l'affichage est unique pour chacun puisqu'on pass un ID unique pour chaque, histoire de montrer que l'on peut, avec un même objet, si l'on déporte certaines variables qui pourraient être des propriétés vers des arguments des méthodes, obtenir des objets avec un comportement différent.

*/

echo $oText ->count();

?>



BRIDGE

Le BRIDGE permet de découpler une interface et son implémentation, de manière à ce qu'elles puissent évoluer séparément.

Il se compose de :

L'ABSTRACTION : définit l'interface abstraite et maintient la référence à l'IMPLEMENTOR

La REFINED ABSTRACTION : le BRIDGE. Etend l'ABSTRACTION.

L'ABSTRACT IMPLEMENTOR : définit l'interface pour l'implémentation des classes.

Le CONCRETE IMPLEMENTOR : les classes...

Un petit exemple où l'on va pouvoir choisir à moindre coût de générer du joli HTML tout neuf, ou du vieil HTML tout pourri... ;-) :


<?php

/**

* ABSTRACT IMPLEMENTOR, interface définissant les méthodes des CONCRETE IMPLEMENTORS

*

*/

interface writeHtml {

    public function writeBold($sWord);

    public function writeItalic($sWord);

}


/**

* CONCRETE IMPLEMENTOR pour le html récent

*

*/

class writeHtmlNew implements writeHtml {

    public function writeBold($sWord) {

        echo htmlspecialchars('<strong>'.$sWord.'</strong>');

    }

    

    public function writeItalic($sWord) {

        echo htmlspecialchars('<em>'.$sWord.'</em>');

    }

}


/**

* CONCRETE IMPLEMENTOR pour le vieux html moche

*

*/

class writeHtmlOld implements writeHtml {

    public function writeBold($sWord) {

        echo htmlspecialchars('<b>'.$sWord.'</b>');

    }

    

    public function writeItalic($sWord) {

        echo htmlspecialchars('<i>'.$sWord.'</i>');

    }

}


/**

* ABSTRACTION, qui définit les méthodes que la REFINDES ABSTRACTION utilisera

*

*/

interface htmlTpl {

    public function writeHtmlBold();

    public function writeHtmlItalic();

}


/**

* REFINED ABSTRACTION, qui joue le rôle de BRIDGE

*

*/

class htmlTplRefined {

    private $oHtmlType;

    private $sWord;

    

    public function __construct($sWord, writeHtml $htmlType) {

        $this ->oHtmlType = $htmlType;

        $this ->sWord = $sWord;

    }

    

    public function writeHtmlBold() {

        $this ->oHtmlType ->writeBold($this ->sWord);

        

    }

    public function writeHtmlItalic() {

        $this ->oHtmlType ->writeItalic($this ->sWord);

    }

}


$oHtmlNew = new htmlTplRefined('test', new writeHtmlNew);

$oHtmlOld = new htmlTplRefined('test', new writeHtmlOld);


$oHtmlNew ->writeHtmlBold();

echo'<br />';

$oHtmlNew ->writeHtmlItalic();


echo'<br />';


$oHtmlOld ->writeHtmlBold();

echo'<br />';

$oHtmlOld ->writeHtmlItalic();

?>



ADAPTER

L'ADAPTER permet d'adapter l'interface d'une classe à une autre interface.

Prenons un exemple de la vie courante : vous avez votre portable et sa recharge, prévu pour des prises françaises.

les prises françaises fournissent de l'électricité. Mais vous êtes en Irlande. Les prises irlandaises fournissent

aussi de l'électricité (si si), mais n'utilisent pas la même interface que les prises française. Vous utilisez donc

un adaptateur pour brancher votre recharge sur la prise irlandaise.

Le petit exemple de code va se baser sur un contexte différent : vous avez trouvé une classe permettant de déplacer une voiture.

Vous avez écrit votre propre interface de déplacement, et avez besoin de créer une classe permettant de déplacer un homme.

Malheureusement, l'interface de la classe de déplacement de voiture n'est pas la même que la votre.

A noter que l'interface à adapter doit possèder au moins toutes les méthodes nécessaires à l'interface adaptée pour que

l'utilisation de ce design pattern soit intéressante, et peut en possèder plus que nécessaire.

Donc, vous adaptez :-) :


<?php

/**

* L'interface que vous devez utiliser

*

*/

interface walk {

    public function walkLeft();

    public function walkRight();

    public function walkForward();

    public function walkBackward();

}


/**

* L'interface à adapter

*

*/

interface drive {

    public function driveLeft();

    public function driveRight();

    public function driveForward();

    public function driveBackward();

}


/**

* La classe implémentant l'interface à adapter

*

*/

class driveCar implements drive {

    protected $aPos = array('X' => 0, 'Y' => 0);

    

    public function driveLeft() {

        $this ->aPos['X'] -= 1;

    }


    public function driveRight() {

        $this ->aPos['X'] += 1;

    }

    

    public function driveForward() {

        $this ->aPos['Y'] += 1;

    }

    

    public function driveBackward() {

        $this ->aPos['Y'] -= 1;

    }

    

    public function getCarPos () {

        return $this ->aPos;

    }

}


/**

* Votre classe ADAPTER

*

*/

class walkGuy extends driveCar implements walk {

    

    public function walkLeft() {

        $this ->driveLeft();

    }

    

    public function walkRight() {

        $this ->driveRight();    

    }

    

    public function walkForward() {

        $this ->driveForward();

    }

    

    public function walkBackward() {

        $this ->driveBackward();

    }

    

    public function getPos() {

        return $this ->getCarPos();

    }

}


/**

* Test

*/

$oGuy = new walkGuy;

print_r ($oGuy ->getPos());

$oGuy ->walkRight();

$oGuy ->walkForward();

print_r ($oGuy ->getPos());

?>



DECORATOR

Le DECORATOR (ou décorateur) sert à ajouter dynamiquement de nouvelles fonctionnalités à un objet. C'est une technique plus souple que

l'héritage, et qui dont peut parfois être très utile (pour simuler du multi-héritage par exemple).

Il se compose de :

- Une INTERFACE pour la classe à décorer

- La classe à décorer

- Un ABSTRACT DECORATOR qui implémente l'INTERFACE de l'objet à décorer

- X CONCRETE DECORATOR

Nous aurons ici 2 exemples. Pourquoi 2 ? Le 1er va vous montrer comment utiliser ce design pattern en PHP. Néanmoins,

par rapport à d'autres langages, PHP ne permet pas aussi facilement d'appliquer cette technique comme elle l'est souvent:

c'est à dire en imbriquant les CONCRETE DECORATOR afin d'hériter de chacunes de leurs spécificités.

Le 2d exemple va donc vous montrer comment on peut faire en PHP, en appliquant un autre design pattern que vous connaissez

tous : la FACTORY.

1er exemple donc : on va dessiner un carré! La classe de base se contente de dessiner un carré dimensionné, sans plus de frioriture.

Les DECORATOR, au nombre de deux, vont, pour l'un, permettre de colorier le carre, et pour l'autre d'y ajouter une bordure.

Par contre, c'est l'un, ou l'autre ! :


<?php

/**

* L'INTERFACE de la classe à décorer

*

*/

interface box {

    public function dim($iWidth, $iHeight);

    public function draw();

}


/**

* La classe à décorer

*

*/

class simpleBox implements box {

    public $aStyle;

    

    public function dim($iHeight, $iWidth) {

        if (!is_int($iWidth) || !is_int($iHeight)) {

            throw new Exception('Parameters must be integers');

        }

        $this ->aStyle['width'] = 'width:'.$iWidth.'px';

        $this ->aStyle['height'] = 'height:'.$iHeight.'px';

    }

    

    public function draw() {

        if (is_null($this ->aStyle['width']) || is_null($this ->aStyle['height'])) {

            throw new Exception('You must dim the box first');

        }

        $sStyle = implode(';', $this ->aStyle);

        echo '<div style="'.$sStyle.';"></div>';

    }

}


/**

* L'ABSTRACT DECORATOR qui implémente l'interface de la classe à décorer

*

*/

abstract class boxDecorator implements box {

    protected $oBox;

    

    public function __construct(box $o) {

        $this ->oBox = $o;

    }

}


/**

* Un premier CONCRETE DECORATOR

*

*/

class coloredBox extends boxDecorator {

    

    public function __construct(box $o, $sColor) {

        parent::__construct($o);

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

    }

    

    public function dim($iHeight, $iWidth) {

        $this ->oBox ->dim($iHeight, $iWidth);

    }

    

    public function draw() {

        $this ->oBox ->draw();

    }

}

/**

* Et un second :-)

*

*/

class borderedBox extends boxDecorator {

    

    public function __construct(box $o, $iBorder, $sBorderStyle, $sBorderColor) {

        parent::__construct($o);

        if (!is_int($iBorder)) {

            throw new Exception('Border thickness must be an integer');

        }

        $this ->oBox ->aStyle['border'] = 'border:'.$iBorder.'px '.$sBorderStyle.' '.$sBorderColor;

    }

    

    public function dim($iHeight, $iWidth) {

        $this ->oBox ->dim($iHeight, $iWidth);

    }

    

    public function draw() {

        $this ->oBox ->draw();

    }

}


/**

* Les tests ! On va dessiner 2 jolis carrés :-)

*/

$oBox = new coloredBox (new simpleBox, 'blue');

$oBox ->dim(50,50);

$oBox ->draw();


$oBox = new borderedBox (new simpleBox, 1, 'dotted', 'red');

$oBox ->dim(50,50);

$oBox ->draw();

?>


Le 2d exemple donc qui va nous permettre de dessiner 1 seul carrés qui aura hérité des spécificités de la classe de base

ET des deux DECORATOR :


<?php

/**

* L'INTERFACE de la classe à décorer

*

*/

interface box {

    public function dim($iWidth, $iHeight);

    public function draw();

}


/**

* La classe à décorer

*

*/

class simpleBox implements box {

    public $aStyle;

    

    public function dim($iHeight, $iWidth) {

        if (!is_int($iWidth) || !is_int($iHeight)) {

            throw new Exception('Parameters must be integers');

        }

        $this ->aStyle['width'] = 'width:'.$iWidth.'px';

        $this ->aStyle['height'] = 'height:'.$iHeight.'px';

    }

    

    public function draw() {

        if (is_null($this ->aStyle['width']) || is_null($this ->aStyle['height'])) {

            throw new Exception('You must dim the box first');

        }

        $sStyle = implode(';', $this ->aStyle);

        echo '<div style="'.$sStyle.';"></div>';

    }

}


/**

* L'ABSTRACT DECORATOR qui implémente l'interface de la classe à décorer

* Notez que cette fois, l'objet boxDecorator::oBox est une propriété statique, et que l'on a plus de constructeur

* A la place, on a une méthode d'usinage :-)

*

*/

abstract class boxDecorator implements box {

    protected static $oBox;

    

    public function getInstance(box $o) {

        self::$oBox = $o;

        return self::$oBox;

    }

}


/**

* Un premier CONCRETE DECORATOR

* Pas non plus de constructeur, mais une méthode d'usinage aussi!

*

*/

class coloredBox extends boxDecorator {

    

    public function getInstance(box $o, $sColor) {

        self::$oBox = parent::getInstance($o);

        self::$oBox ->aStyle['color'] = 'background-color:'.$sColor;

        return self::$oBox;

    }

    

    public function dim($iHeight, $iWidth) {

        self::$oBox ->dim($iHeight, $iWidth);

    }

    

    public function draw() {

        self::$oBox ->draw();

    }

}


/**

* Et un second :-)

*

*/

class borderedBox extends boxDecorator {

    

    public function getInstance(box $o, $iBorder, $sBorderStyle, $sBorderColor) {

        self::$oBox = parent::getInstance($o);

        if (!is_int($iBorder)) {

            throw new Exception('Border thickness must be an integer');

        }

        self::$oBox ->aStyle['border'] = 'border:'.$iBorder.'px '.$sBorderStyle.' '.$sBorderColor;

        return self::$oBox;

    }

    

    public function dim($iHeight, $iWidth) {

        self::$oBox ->dim($iHeight, $iWidth);

    }

    

    public function draw() {

        self::$oBox ->draw();

    }

}


/**

* Le test ! On va cette fois ne dessiner qu'un seul carré, utilisant les spécificités des deux DECORATOR! (et de la classe de base, évidemment)

*/

$oBox = borderedBox::getInstance(coloredBox::getInstance(new simpleBox, 'blue'), 1, 'dotted', 'red');

$oBox ->dim(50,50);

$oBox ->draw();

?>


31 août 2007 18:58:15 :
Me suis trompé de fichier...;-)
signaler à un administrateur
Commentaire de codefalse le 06/09/2007 07:46:48 administrateur CS

amazing !! :)

signaler à un administrateur
Commentaire de malalam le 06/09/2007 12:41:19 administrateur CS

Thanks :-)

signaler à un administrateur
Commentaire de craso le 09/09/2007 10:38:23

Très intéressant tout ça, et je pense que ces design patterns servent surtout dans des projets importants. J'ai envie de faire un calendrier sportif pour la course à pied ou chaque coureur note ses entrainements, avec entre autre, la météo, le temps de course, son propre poids, etc. Il en résultera des graphiques en SVG qui montreront l'évolution du coureur, pour lui uniquement. Je ne suis pas sûr qu'utiliser un design pattern soit ici une bonne idée. Après tout je débute en la matière alors si vous avez une idée, je vous écoute!

signaler à un administrateur
Commentaire de malalam le 09/09/2007 10:58:44 administrateur CS

Hello,

non, les design patterns ne se limitent pas aux projets importants.
Les design patterns sont des solutions à des problèmes. Et ces problèmes, ont peut les rencontrer dans de petits scripts orientés objet.
Un exemple tout con : une classe d'abstraction de bdd, ce n'est pas un projet important, c'est un outil qui peut s'incorporer dans des projets importants ou mineurs.
Et pourtant, dans une telle classe, la plupart des développeurs vont utiliser un design pattern : le singleton, ou mieux, le multiton.
Il ne faut pas réflêchir comme ça. Il faut simplement garder en tête ces techniques, les comprendre, et le jour où on en aura besoin, on le saura. Les connaître facilite aussi la modélisation d'un projet/package/outil. Cela donne des idées. Le builder (monteur) par exemple peut s'utiliser dans bien des cas. Le proxy ou le flyweight sont aussi des exemples qui peuvent servir dans des scripts mineurs.
L'observer, qui est dans la partie 3 de ce tuto (je la publierai le plus vite possible) est un design pattern particulièrement intéressant et utile dans bien des cas.
Dans ton calendrier, par exemple, il pourrait te servir à déclencher des évènements justement (j'ai déjà mis en ligne un exemple de ce design pattern dans les sources : observer pattern, cherche dans mes sources si ça t'intéresse).

Ce sont des techniques de programmation. Ce ne sont pas des solutions miracles et complexes uniquement compréhensibles et utilisables par des gourous du php dans des projets d'envergure.

Vraiment :-) C'est ce que j'essaye de montrer ici, en même temps que de donner des clefs aux prograimmeurs du dimanche pour qu'ils codent mieux, exploitent au mieux les possibilités offertes par php5, et cessent d'avoir peur quand on parle de POO, d'itérateurs, de design patterns...tout ça n'a rien de sorcier :-)
J'en ai aussi un peu assez de voir des débutants prendre de mauvaises habitudes, en apprenant un php qui ressemble à du php3, simplement parce que sur le net, on trouve encore trop de vieux projets dinosaures, codés il y a très longtemps, et n'ayant jamais su évoluer. Il y a très peu de projets open source exploitant les vastes possibilités offertes par php5. Très peu de sources tout court, d'ailleurs. Et quand un débutant trouve quand même une telle source, ça lui fait peur, parce qu'il n'y est pas habitué...et finalement, il reste à son php4 vieillissant, en codant façon php3 en plus.
C'est dommage...et si je peux via ces tutos en convaincre 2 ou 3 que ce n'est pas si inabordable de coder proprement et évolué, ben j'ai tout gagné :-)

Alors réflêchis-y quand même ;-)

signaler à un administrateur
Commentaire de coucou747 le 23/09/2007 21:05:28

visiteur c'est beau aussi, j'en ai pose un pour explorer mes templates xml
elle est fun ton interface "interface iPeople" :)

tu devrais generer les shemats qui corresppondent a ton code, en plus, t'as commente pour donc t'as juste une ligne de commande a lancer pour pouvoir le faire...

signaler à un administrateur
Commentaire de DiGhan le 26/09/2007 12:04:44

Une très bonne initiative.
Tout y est, bravo !

signaler à un administrateur
Commentaire de morpheus57 le 26/09/2007 15:12:07

Alors là je dis bravo ! ! !

Merci pour tout ce que tu apportes à PHP... La bataille va être longue pour lui faire perdre son image de langage de débutant mais c'est avec des tutos comme ça que l'on peut y arriver.

Merci ! ! !

signaler à un administrateur
Commentaire de malalam le 26/09/2007 17:37:51 administrateur CS

@Coucou => Bonne idée les schémas! e vais me pencher là-dessus. Mais avant il faut que je trouve le temps de finaliser la partie 3...;-) Je réponds du même coup à ton commentaire sur la partie 1 : la mise en page...en fait, j'ai fait ce truc avec Textpad, au départ. Puis comme un des format accepté par les tutos de CS est le docx, j'ai importé mon texte de texpad sous docx et je l'ai reformatté. Mais la moulinette d'affichage des tutos a apparemment tendance à modifier certains trucs (je pense aux sauts de ligne...), ou bien je n'ai pas tout compris au format docx :-) Bref...je ne remettrai pas en page d'ici un moment, c'est long et pénible à faire.
Pour Visiteur, oui, je l'aime bien aussi. Il est très pratique en plus!

@Dighan => ben merci :-) Mais tout n'y est pas ;-) J'ai fait l'impasse sur quelques design patterns exotiques, et il manque toujours la partie 3 (presque finie enfait, me reste la mise en page et 2-3 design patterns je crois...c'est la partie la plus longue des 3).

@Morpheus => merci aussi :-) Je t'avoue que j'aimerais que PHP perde cette image...ou du moins, que de plus en plus d'amateurs prennent de bonnes habitudes, se mettent à mieux coder, se mette à l'heure du PHP5 style, en fait, et abandonne le PHP3 style que l'on voit beaucoup, beaucoup plus souvent, même de nos jours.

signaler à un administrateur
Commentaire de tucsoufle le 30/09/2007 15:08:05

Salut à tous,

je n'ai plus beaucoup de temps pour venir réguliérement sur phpcs comme avant
mais je voulais quand même venir pour te féliciter malalam pour ce(ces) trés bon tuto(s)
sur les design patterns... ils vont en faire saliver quelques uns. Bravo!

je ne mettrais pas de note parce que c'est pas bien de faire du léche botte à un admin ;)

continuez tous comme ca pour faire évoluer le php et à bientot...

signaler à un administrateur
Commentaire de malalam le 06/10/2007 20:10:21 administrateur CS

Hello,

merci Tuc :-)
N'empêche, ne pas donner une bonne note à un admin, ça peut-être dangereux... ;-)

La partie 3 est terminée, sinon. Il y a quelques soucis avec l'upload des tutos sur le site, là, donc je ne peux pas vous a montrer...mais pour avoir une petite idée, on y trouvera entre autres des exemples appliqués sur :
Le MEMENTO, avec un exemple qui vous montrera comment faire des rollback sur un code, exactement comme vous Le feriez sur des requêtes SQL ;-)
L'INTERPRETER dont l'exemple vous fera un petit revival de ce vieux langage qui permettait de faire des dessins avec des commandes simples, le LOGO.
Le MEDIATOR et son exemple simulant un combat entre héro et un méchant, chacun accompagné de leur familier qui ressent ce que son maître ressent (c'est mystérieux, hein ?)

Have fun.

signaler à un administrateur
Commentaire de malalam le 06/10/2007 20:11:13 administrateur CS

Et plein d'autres, hein! La partie 3 est la plus grosse partie (la plus comlexe aussi).

signaler à un administrateur
Commentaire de codefalse le 06/10/2007 23:17:57 administrateur CS

vivement que l'upload remarche !!! eh oh !! :p :)

signaler à un administrateur
Commentaire de FredT le 30/12/2007 12:43:07

COUCOU :: "tu devrais generer les shemats qui corresppondent a ton code, en plus, t'as commente pour donc t'as juste une ligne de commande a lancer pour pouvoir le faire..."
Ca m'intéresse carrément je cherche qq-chose simple et gratuit pour le faire depuis déjà qq temps, tu pourrais en dire davantage ?

MALALAM :: Bien résumé, très bien expliqué, superb pour s'initié sur le sujet avec orientation PHP, Mais faut avouer qu'un tuto sur CS c'est vraiment pas lisible et utilisable pas de toc, aucun point de repère ... Domage pour un sujet aussi intérressant que celui-ci, que tu traites très bien et simplement

signaler à un administrateur
Commentaire de malalam le 30/12/2007 15:10:48 administrateur CS

@FredT : merci :-) Oui, le système de tuto de CS n'autorise malheureusement pas l'upload de fichiers (html, pdf..) qui faciliterait la création de tutos plus sympas et plus interactifs. Peut-être un jour... :-)
Bref, merci en tous cas.

signaler à un administrateur
Commentaire de jobegood le 24/01/2008 19:17:25

Bon bah oui, bravo et merci beaucoup, tutos très intéressants. J'ai appris énormément. Mais j'ai trouvé une petite erreur, dans la redéfinition de la SplObjectStorage... Le commentaire dit : "Surtout pour voir comment elle fonctionne"!!! Et bien un foreach ne fonctionne pas là-dessus.
Pour la fonction Valid(), il faut remplacer :
return key($this->storage) !== false;    
par
return current($this->storage) !== false;
et comme ça , ça fonctionne ! Un itérateur qui ne tient pas un foreach... J'en ai encore la migraine ...
Bon, mais ça vient même pas ébrécher ce fabuleux boulot, encore merci. Et puis je suis tellement fier d'avoir trouvé un truc !

signaler à un administrateur
Commentaire de malalam le 26/01/2008 23:01:48 administrateur CS

@JOBEGOOD => ce doit être un problème de version de PHP car cela fonctionne très bien chez moi (à vrai dire je suis surpris car cela fonctionne de la version php 5.0.4 à la 5.3 pour moi). D'autant plus que ce n'est pas une "redéfinition", mais une copie conforme du code écrit par les développeurs de PHP. Et que je ne vois pas ce qui peut bloquer dans cette ligne, d'ailleurs...que se passe-t-il pour toi ?
Un itérateur tient très bien le foreach (normalement, hein...ils sont faits pour).

Ceci dit, merci :-)

signaler à un administrateur
Commentaire de jobegood le 28/01/2008 00:01:45

Salut,
Je me disais bien... J'utilise une version PHP 5.2.3.
Si je laisse le key($this->storage), j'ai une boucle infinie pour un foreach ($StockLesObjets as $Cle=> $Valeur)(enfin dans mon application ! ;-) ). J'ai épluché la doc PHP qui effectivement n'indique pas de valeur de retour type false en cas de "non validité", pour la fonction key() - ce qui ne veut pas dire qu'elle ne le fait pas. Ce qui expliquerais, du moins je le croyais, que la boucle ne s'arrêtait pas. Par contre la fonction current() est explicitement déclarée comme retournant false dans ce cas. Ajouté à cela l'exemple de la doc php rubrique langage, classes et objets en php5, sur l'iteration d'objet, je me suis dit : mais c'est bien sûr ! (j'ai pas été très original sur ce coup)
public function valid() {
       $var = $this->current() !== false;
       echo "valid: {$var}\n";
       return $var;
   }
J'ai modifié dans mon script et ça marchait. Et je me suis empressé de publier ma découverte, bon, un peu rapidement, désolé.
Et puis bah pourquoi tjrs vouloir refaire ce qui est déjà fait ? Bon, on apprend, comme ça, la preuve. En tous cas, moi, j'apprends en essayant. Et en me trompant.
Je ne sais pas pourquoi ça m'arrive à moi. Je vais l'essayer dans le vide, hors de mon application. Mais ça ne me donnera pas d'explication.
J'ai changé mon appli depuis (j'ai remis if (!class_exist('SplObjectStorage')) :-) ). Peut-être que l'utilisation de surcharges comme __set($Parametre, $Valeur){$this->current()->{$Parametre}=$Valeur;} ou __get($Parametre){return $this->current()->{$Parametre}} a l'interieur de la classe 'SplObjectStorage' peuvent y être pour quelque chose ? Promis, c'est tout ce que j'ai rajouté. Mais je ne vois pas ce qui interfererait.
Mais bon, je sais qu'en changeant key pour current, ça marchait. Je m'enfonce !
Grand merci en tout cas d'avoir prêté attention à ma remarque et d'y avoir répondu. Je trouve ça très agréable d'avoir des tutos géniaux et que de surcroit ceux qui les ont écrit  restent disponible à ceux qui les lisent. J'arrête avec les fleurs. :-D

signaler à un administrateur
Commentaire de jobegood le 28/01/2008 14:41:36

Il a fallu que je l'ecrive pour me rendre compte de l'enormité ! Bien sûr que ça coince si je ne verifie pas l'existence de ce dont je force la demande (__get(..)). Je ne saurai pas dire pourquoi current et pas key, mais je peux imaginer que ça pose probleme. J'aurai du faire un if (isSet($this->current()->{$Parametre})) dans le __get, sinon l'itérateur m'en fournit autant (et plus) que j'en veux. Euh... C'est ça, docteur ?
Tout cela n'a plus grand chose à voir avec le tuto qui lui, si la perfection existait, en ferait partie. Je ne serai donc pas véxé si ces commentaires disparaissaient, sauf si ils peuvent servir à d'autres (?). Vérifiez l'existence de vos variables, et de vos erreur, avant   de poster !!!
Sinon, je me suis mis à PHP il y a peu, et je découvre CS avec grand plaisir et intérêt. Bravo à tous, c'est formidable ce que vous faites. J'espère que je serai bientôt en mesure d'apporter mes petites pierres à cet édifice, ça ne serait qu'un juste retour des choses...

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 G1

Entre 449€ et 449€


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 : 2,980 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é.