Cinq motifs supplémentaires de conception pour PHP

| Nathan A. Good | Guillaume Rossolini (traducteur) |

Le modèle orienté objet de PHP 5 vous donne la capacité d'implémenter des motifs de conception pour améliorer la conception de votre code. Lorsque vous améliorez la conception de votre code de cette manière, il devient plus lisible, plus aisément maintenable et plus robuste au moment d'absorber les modifications.

Note du traducteur : La première publication de cet article eut lieu en anglais sur IBM developerWorks (www.ibm.com/developer). Tous droits réservés par IBM et par l'auteur. Traduction par Guillaume Rossolini avec l'aimable autorisation d'IBM et de l'auteur. Voir aussi l'article Cinq motifs classiques de conception pour PHP.

Commentez ♪

- Titre : Cinq motifs supplémentaires de conception pour PHP

- Auteurs : Nathan A. Good, Guillaume Rossolini (traducteur)

- Parution : 16 mai 2008

- Licence : Copyright ® 2008 IBM et Nathan A. Good. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Pièges

- I-B. Prérequis

- II. Le motif Adapter

- II-A. Méthode alternative

- III. Le motif Iterator

- IV. Le motif Decorator

- V. Le motif Delegate

- VI. Le motif State

- VII. Résumé

- VIII. Téléchargement

I - Introduction

Le livre Design Patterns m'a introduit à la notion que de tels concepts existent. À l'époque, j'apprenais encore l'Orienté Objet (OO), de nombreux concepts du livre me semblaient donc difficiles à saisir. Cependant, en devenant plus familier des concepts OO (en particulier l'utilisation des interfaces et l'héritage), j'ai commencé à comprendre la valeur réelle des motifs de conception. En tant que développeur d'applications, vous pouvez très bien faire toute votre carrière sans connaître le nom d'un seul des motifs, ou encore comment et quand ils sont utilisés. Néanmoins, j'estime qu'une bonne connaissance pratique de ces motifs (ainsi que ceux présentés dans l'article Cinq motifs classiques de conception pour PHP), vous permet de faire deux choses :

- réduire le temps des communications ;

- réduire la fréquence des leçons difficiles.

Dans le premier cas : Si vous connaissez des motifs de conception, vous êtes en mesure de construire des applications OO robustes plus rapidement. Lorsque votre équipe entière connaît les divers motifs, vous pouvez alors avoir des conversations très rapides. Vous n'avez plus besoin de parler de toutes les classes que vous utiliseriez ici ou là ; au lieu de cela, vous pouvez parler en termes de motifs les uns aux autres. « Eh bien, j'utilise un Singleton ici, puis un Itérateur pour parcourir une collection de mes objets et… » est bien, bien plus rapide que de lister toutes les classes, méthodes et interfaces qui fondent ces motifs. À elle seule, cette efficacité de communication peut valoir le temps nécessaire à quelques sessions pour étudier les motifs ensemble, en équipe.

Dans l'autre cas : Chaque motif décrit une méthode reconnue pour résoudre un problème standard. Ainsi, vous n'avez plus besoin de vous soucier de l'exactitude de votre conception, tant que vous avez choisi le motif qui répond à vos besoins.

I-A - Pièges

En anglais, il existe le proverbe suivant : « Lorsque vous portez un marteau, tout semble être un clou. » Lorsque vous apprenez un motif que vous aimez bien, vous pourriez essayer de l'utiliser à tort et à travers, même dans des situations inadaptées. Souvenez-vous de considérer les objectifs des motifs que vous apprenez, et de ne pas forcer l'utilisation de motifs dans des portions de votre application pour le simple fait de les utiliser.

Cet article couvre cinq motifs que vous pouvez utiliser pour améliorer votre code PHP. Chaque motif couvre un scénario bien précis. Le code PHP des motifs est disponible dans la section Téléchargement.

I-B - Prérequis

Pour tirer le meilleur parti de cet article et pour utiliser les exemples, installez ceci sur votre machine :

- PHP 5 ou ultérieur (cet article a été écrit avec PHP 5.2.4) ;

- un programme de décompression tel que Winzip (pour décompresser le code de l'archive).

Bien que vous n'ayez pas absolument besoin d'un éditeur autre qu'un éditeur de texte, il me semble réellement utile d'avoir une coloration syntaxique et une correction syntaxique automatiques. Les exemples de cet article sont écrits à l'aide d'Eclipse PHP Development Tools (PDT).

II - Le motif Adapter

Utilisez le motif Adapter (adaptateur) lorsque vous avez besoin de convertir un objet d'un type vers un autre type. Les développeurs s'en chargent par du code d'assignation comme indiqué en Listing 1. Le motif Adapter est un bon moyen pour nettoyer ce code et pour réutiliser les assignations à d'autres endroits. Il cache également le code d'assignation, ce qui peut grandement simplifier les choses si modifiez le format des données en même temps.

Listing 1. Utiliser du code pour assigner des valeurs entre des objets

class AddressDisplay
{
private $addressType;
private $addressText;

public function setAddressType($addressType)
{
$this->addressType = $addressType;
}

public function getAddressType()
{
return $this->addressType;
}

public function setAddressText($addressText)
{
$this->addressText = $addressText;
}

public function getAddressText()
{
return $this->addressText;
}
}

class EmailAddress
{
private $emailAddress;

public function getEmailAddress()
{
return $this->emailAddress;
}

public function setEmailAddress($address)
{
$this->emailAddress = $address;
}
}

$emailAddress = new EmailAddress();
/* Remplir l'objet EmailAddress */
$address = new AddressDisplay();
/* Voici le code d'assignation, où j'assigne les valeurs
d'un objet à l'autre... */
$address->setAddressType("email");
$address->setAddressText($emailAddress->getEmailAddress());

Cet exemple utilise un objet AddressDisplay pour afficher une adresse à un utilisateur. Cet objet a deux parties : le type de l'adresse et une chaîne correctement formée de l'adresse.

Après avoir implémenté le motif (voir en Listing 2), le script PHP n'a plus besoin de savoir dans le détail comment l'objet EmailAddress est converti en objet AddressDisplay. C'est une bonne chose, surtout si l'objet DisplayAddress est modifié ou si les règles de passage d'un objet à l'autre changent. Rappelez-vous que l'un des avantages de concevoir son code de façon modulaire, est de tirer parti de n'avoir que peu de modifications à faire lorsque quelque chose change dans le domaine des traitements ou lorsque vous devez ajouter une fonctionnalité au programme. Pensez-y même lorsque vous effectuez des tâches triviales comme assigner des propriétés d'un objet à un autre.

Listing 2. Utiliser le motif Adapter

class EmailAddressDisplayAdapter extends AddressDisplay
{
public function __construct($emailAddr)
{
$this->setAddressType("email");
$this->setAddressText($emailAddr->getEmailAddress());
}
}

$email = new EmailAddress();
$email->setEmailAddress("user@example.com");

$address = new EmailAddressDisplayAdapter($email);

echo($address->getAddressType() . "\n") ;
echo($address->getAddressText());

Figure 1. Diagramme de classes du motif Adapter

II-A - Méthode alternative

Une méthode alternative d'écrire un Adapter (préférée par certaines personnes) est d'implémenter une interface pour adapter le comportement, plutôt qu'étendre une classe. C'est une méthode très propre de créer un adaptateur et qui n'a pas les inconvénients d'étendre une classe. Cependant, l'un des inconvénients d'utiliser une interface est que vous devez ajouter l'implémentation à la classe adaptateur, tel qu'en Figure 1 :

Figure 2. Le motif Adapter (utiliser une interface)

III - Le motif Iterator

Le motif Iterator (itérateur) propose une solution pour parcourir une collection ou un tableau d'objets. C'est particulièrement utile si vous voulez parcourir différents types d'objets dans la collection.

Regardez l'exemple d'adresse e-mail et postale dans le Listing 1. Avant d'ajouter un motif Iterator, si vous parcourez les adresses d'une personne, vous pourriez parcourir les adresses postales et les afficher, puis parcourir les adresses e-mail et les afficher également, puis parcourir les adresses de messagerie instantanée et les montrer à leur tour. Convenez que c'est très laid !

Au lieu de cela, en implémentant un itérateur, tout ce que vous avez à faire est d'appeler while($itr->hasNext()) et de s'occuper de l'élément renvoyé par $itr->next(). Un exemple de l'un des itérateurs est montré en Listing 3. Un Iterator est puissant, car vous pouvez ajouter de nouveaux types d'éléments à parcourir sans devoir changer le code qui les parcourt. Dans l'exemple Person, vous pourriez notamment ajouter un tableau d'adresses de messagerie instantanée ; en actualisant uniquement l'Iterator, vous n'avez besoin de changer aucun code qui affiche en boucle les adresses.

Listing 3. Utiliser le motif Iterator pour parcourir des objets

class PersonAddressIterator implements AddressIterator
{
private $emailAddresses;
private $physicalAddresses;
private $position;

public function __construct($emailAddresses)
{
$this->emailAddresses = $emailAddresses;
$this->position = 0;
}

public function hasNext()
{
if ($this->position >= count($this->emailAddresses) ||
$this->emailAddresses[$this->position] == null) {
return false;
} else {
return true;
}
}

public function next()
{
$item = $this->emailAddresses[$this->position];
$this->position = $this->position + 1;
return $item;
}

}

Si l'objet Person est modifié pour renvoyer une instance de l'interface AddressIterator, le code applicatif qui utilise l'itérateur n'a pas besoin d'être modifié si l'implémentation est étendue au parcours d'objets supplémentaires. Vous pouvez utiliser un itérateur composé qui encapsule les itérateurs qui parcourent tous les types d'adresses comme celui montré en Listing 3. Un exemple est disponible (cf. les téléchargements).

Figure 3. Diagramme de classes du motif Iterator

Note du traducteur : la SPL permet également de construire facilement un motif Iterator en PHP.

IV - Le motif Decorator

Regardez le code en Listing 4. Son objectif est d'ajouter quelques fonctionnalités à une voiture pour un site « Construisez votre propre voiture ». Chaque modèle de voiture a davantage de fonctionnalités et un coût associé. Avec seulement deux modèles, il serait trivial d'ajouter ces fonctionnalités avec des instructions if/else. Cependant, dès qu'un nouveau modèle survient, vous auriez à retourner dans le code et à vous assurer qu'il fonctionne correctement pour le nouveau modèle.

Listing 4. Utiliser le motif Decorator pour ajouter des fonctionnalités

require('classes.php');

$auto = new Automobile();

$model = new BaseAutomobileModel();

$model = new SportAutomobileModel($model);

$model = new TouringAutomobileModel($model);

$auto->setModel($model);

$auto->printDescription();

Voici le motif Decorator (décorateur), qui vous permet d'ajouter des fonctionnalités à AutomobileModel d'une manière propre et organisée. Chaque classe reste concernée uniquement par son prix, par ses options et par la manière dont elle est ajoutée au modèle de base.

Figure 4. Diagramme de classes du motif Decorator

Un avantage du motif Decorator est que vous pouvez aisément brancher plus d'un décorateur à la fois à la base.

Si vous avez beaucoup travaillé avec des flux, vous avez utilisé un Decorator. La plupart des conduits de flux, comme un flux de sortie, sont des décorateurs qui prennent un flux d'entrée et qui le décorent en ajoutant des fonctionnalités (comme prendre des fichiers comme point d'entrée, ou bien une mémoire tampon, etc.).

V - Le motif Delegate

Le motif Delegate (délégué) donne une méthode de délégation de comportement d'après divers critères. Regardez le code en Listing 5. Il contient diverses conditions. Le code choisit le bon type d'objet pour traiter la requête en fonction de la condition.

Listing 5. Utiliser des conditions pour transmettre les requêtes d'envoi

pkg = new Package("Heavy Package");
$pkg->setWeight(100);

if ($pkg->getWeight() > 99)
{
echo("Envoi de " . $pkg->getDescription() . " par train.");
} else {
echo("Envoi de " . $pkg->getDescription() . " par camion.");
}

Avec un motif Delegate, un objet internalise cette étape de transmission en affectant une référence interne lorsqu'une méthode est appelée, comme useRail() dans le Listing 6. C'est particulièrement pratique si les critères changent pour traiter différents paquetages ou si on ajoute une nouvelle méthode d'envoi.

Listing 6. Utiliser le motif Delegate pour transmettre les requêtes d'envoi

require_once('classes.php');

$pkg = new Package("Heavy Package");
$pkg->setWeight(100);

$shipper = new ShippingDelegate();

if ($pkg->getWeight() > 99)
{
$shipper->useRail();
}

$shipper->deliver($pkg);

Le motif Delegate offre l'avantage que le comportement puisse changer dynamiquement en appelant useRail() ou useTruck() pour changer la classe de traitement.

Figure 5. Diagramme de classes du motif Delegate

VI - Le motif State

Le motif State (état) est similaire au motif Command mais l'intention est très différente. Voyez le code ci-dessous :

Listing 7. Utiliser du code pour construire un robot

class Robot
{

private $state;

public function powerUp()
{
if (strcmp($state, "poweredUp") == 0)
{
echo("Déjà allumé...\n");
/* Implémentation... */
} else if ( strcmp($state, "powereddown") == 0) {
echo("Allumage...\n");
/* Implémentation... */
}
}

public function powerDown()
{
if (strcmp($state, "poweredUp") == 0)
{
echo("Allumage...\n");
/* Implémentation... */
} else if ( strcmp($state, "powereddown") == 0) {
echo("Déjà allumé...\n");
/* Implémentation... */
}
}

/* etc... */

}

Dans ce code, le code PHP représente un système d'exploitation pour un puissant robot qui devient une voiture. Le robot peut être allumé, éteint, devenir un robot quand c'est un véhicule et vice versa. Le code est correct pour le moment, mais vous voyez qu'il peut devenir complexe si l'une des règles change ou si un nouvel état entre en jeu.

Regardez maintenant le Listing 8, qui a la même logique pour traiter les états du robot, mais qui cette fois met la logique dans le motif State. Le code du Listing 8 fait la même chose que le code original, mais la logique de gestion des états a été mise dans un objet pour chacun d'eux. Afin d'illustrer les avantages à utiliser un motif State, imaginez qu'après un moment, les robots découvrent qu'ils ne devaient pas d'éteindre lorsqu'ils sont en mode robot. En fait, s'ils s'éteignent, ils doivent d'abord se changer en véhicules. S'ils sont déjà en mode véhicule, le robot s'éteint simplement. Avec le motif State, les changements sont très simples.

Listing 8. Utiliser le motif State pour traiter l'état du robot

$robot = new Robot();
echo("\n");
$robot->powerUp();
echo("\n");
$robot->turnIntoRobot();
echo("\n");
$robot->turnIntoRobot(); /* Celui-ci me donnera simplement un message */
echo("\n");
$robot->turnIntoVehicle();
echo("\n");

Listing 9. Légères modifications à l'un des objets état (fonction powerDown)

class NormalRobotState implements RobotState
{
private $robot;

public function __construct($robot)
{
$this->robot = $robot;
}

public function powerUp()
{
/* implémentation... */
}
public function powerDown()
{
/* Changer d'abord en véhicule */
$this->robot->setState(new VehicleRobotState($this->robot));
$this->robot->powerDown();
}

public function turnIntoVehicle()
{
/* implémentation... */
}

public function turnIntoRobot()
{
/* implémentation... */
}
}

Une chose qui n'est pas évidente en regardant la Figure 6 est que chaque objet du motif State a une référence à l'objet de contexte (le robot), tel que chaque objet puisse changer son état dans l'objet approprié.

Figure 6. Diagramme de classes du motif State

VII - Résumé

Utiliser les motifs de conception dans votre code PHP est une manière de le rendre plus lisible et maintenable. En utilisant des motifs établis, vous tirez parti de solutions standardisées qui permettent à d'autres développeurs d'une équipe de comprendre les objectifs de votre code. Cela vous permet également de bénéficier du travail effectué par d'autres développeurs, ainsi vous n'avez pas à apprendre les pénibles leçons des idées de conception qui ne fonctionnent pas.

VIII - Téléchargement

Voici le code : téléchargement par FTP ou HTTP.