() translation by (you can also view the original English article)
Vous vous êtes toujours demandé ce que sont ces fameux Design Patterns ? Dans cet article, je vais vous les présenter, démontrer leur importance à travers quelques exemples en PHP, pour déterminer quand et comment les utiliser.
Les Motifs de Conception (Design Patterns) : qu'est-ce que c'est ?
Les Motifs de Conception (Design Patterns en anglais) sont des solutions optimisées et ré-utilisables pour des problèmes quotidiens de programmation. Il ne s'agit pas à proprement parler de classes ou de librairies que nous pourrions installer directement dans notre système ; c'est bien plus que ça. Parlons plutôt d'une architecture qui s'impose nécessairement à une situation particulière. Il ne s'agit pas non plus d'une particularité d'un langage de programmation. Cette caractéristique essentielle devrait se trouver dans la plupart, sinon dans tous les langages de programmation, en fonction de leurs propres spécificités. A noter : qu'elles que soient les motifs de conception envisagés, il y a un revers à leur utilisation : placés abusivement au mauvais endroit, les résultats peuvent être désastreux du genre à vous arracher les cheveux. Par contre, utilisés au bon endroit dans votre code et ils peuvent vous sauver la vie !
Les trois principales familles de design patterns :
- structurelles (structural)
- constructives (creative)
- comportemantales (behavioural)
Les motifs structuraux assurent généralement les relations entre des entités, afin que ces entités travaillent ensemble aisément.
Les motifs constructifs déterminent des mécanismes d'instanciation, facilitant la création d'objets selon la situation.
Les motifs comportementaux sont utilisés dans les échanges entre entités et facilitent leur façon de communiquer entre elles.
Pourquoi devrions-nous les utiliser ?
Les Motifs de Conception sont - par principe - conçus comme des solutions avancées pour contourner la plupart des obstacles rencontrés en programmation. De nombreux développeurs se sont frottés à ces problèmes, et ils ont adoptés ces Motifs pour y remédier. Si vous-même êtes bloqué, pourquoi tenter de ré-inventer la roue, dès lors qu'elle existe déjà ?
Exemple
Imaginons que vous devez trouver le moyen d'unifier deux classes ayant chacune des opérations distinctes selon un contexte précis. Ces deux classes sont extrêmement sollicitées par le système à différents endroits du code, avec une difficulté graduelle à retirer ces classes et à modifier leur code. Rajoutez aussi que la moindre modification nécessite l'obligation de les tester, et que ces quelques transformations - dans un système interagissant avec plusieurs composants - dégagent à coup sûr de nouvelles erreurs. A l'inverse de cette démarche, vous implémentez une correction axée autour de différents Motifs tels que la Strategy Pattern et l'Adapter Pattern, qui gèrent parfaitement ce genre de scénarios.
1 |
|
2 |
<?php
|
3 |
class StrategyAndAdapterExampleClass { |
4 |
private $_class_one; |
5 |
private $_class_two; |
6 |
private $_context; |
7 |
|
8 |
public function __construct( $context ) { |
9 |
$this->_context = $context; |
10 |
}
|
11 |
|
12 |
public function operation1() { |
13 |
if( $this->_context == "context_for_class_one" ) { |
14 |
$this->_class_one->operation1_in_class_one_context(); |
15 |
} else ( $this->_context == "context_for_class_two" ) { |
16 |
$this->_class_two->operation1_in_class_two_context(); |
17 |
}
|
18 |
}
|
19 |
}
|
Assez simple, non ? Penchons nous maintenant sur la Strategy Pattern.
Le Motif Stratégie (Strategy Pattern)

Le Motif Stratégie (Strategy Pattern) fait partie des Motifs de Conception comportementaux vous permettant de définir les moyens d'actions d'un programme, selon un contexte spécifique, au cours de son exécution. Vous encapsulez deux algorithmes différents au sein de deux classes et vous déterminez lors de leur exécution quelle sera la stratégie appropriée à adopter.
Dans notre exemple ci-dessus, la Stratégie est déterminée selon une valeur quelconque de la variable $context, au moment où la classe a été instanciée. Si vous lui attribuez la valeur class_one, la class_one sera déclenchée, et vice-versa.
Génial, mais quand dois-je l'utiliser ?

Imaginons que vous développez actuellement une classe pouvant à la fois mettre à jour et créer l'enregistrement d'un nouvel utilisateur. Elle a toujours besoin des mêmes entrées (nom, adresse, téléphone portable, etc.), mais en fonction de la situation, elle aura besoin de fonctions différentes quand elle mettra à jour ou qu'elle initialisera cet utilisateur. Aussi, vous pourriez sûrement écrire une conditionnelle if-else pour y parvenir, cependant, qu'auriez-vous besoin pour ré-utiliser cette classe ailleurs dans votre code ? Précisemment ici, vous devriez ré-écrire à chaque fois cette condition if-else. Ne serait-il pas plus judicieux de préciser seulement le contexte ?
1 |
|
2 |
<?php
|
3 |
class User { |
4 |
|
5 |
public function CreateOrUpdate($name, $address, $mobile, $userid = null) |
6 |
{
|
7 |
if( is_null($userid) ) { |
8 |
// Si l'utilisateur n'existe pas encore, création d'un nouvel enregistrement.
|
9 |
} else { |
10 |
// Si l'utilisateur existe, mettre directement à jour les informations concernant la variable userid
|
11 |
}
|
12 |
}
|
13 |
}
|
Aussi, l'habituel Motif Stratégie nécessite d'encapsuler vos algorithmes à l'intérieur d'une autre classe. Mais dans ce cas précis, l'écriture d'une autre classe est dispendieuse. Souvenez-vous qu'il n'est pas indispensable de suivre ce modèle à la lettre. Toute variation du code est permise tant que le concept de base demeure et permet de résoudre un probème.
Le motif Adaptateur (Adapter Pattern)

Le Motif Adaptateur (Adapter Pattern) fait partie de la famille des Motif de Conception structuraux qui autorisent le recyclage d'une classe avec une interface différente, et permet d'être exploitée par le système selon différents types d'appel.
Elle permet aussi d'altérer quelques entrées reçues par la classe cliente, pour les rendre sous une forme compatible avec les fonctions de l'adaptateur.
Comment l'utilise-t-on ?

Une autre façon de qualifier la classe Adapter est l'enrobage (wrapper), qui vous permet basiquement "d'enrober" vos actions dans une classe et les reprendre dans les situations appropriées. Un exemple classique pourrait être lors de la création d'une classe principale pour des classes de tableaux. Au lieu de faire appel à des classes différentes de tableaux avec leurs méthodes, une à une, vous pourriez encapsuler toutes ces méthodes en une seule au sein d'une classe Adapter. Ça ne se limite pas au ré-emploi de n'importe quelle action que vous choisissez, cette classe vous empêche toute ré-écriture de code pour une action toujours disponible, n'importe où.
Comparez ces deux implémentations.
Une approche sans l'Adaptateur
1 |
|
2 |
<?php
|
3 |
$user = new User(); |
4 |
$user->CreateOrUpdate( //inputs ); |
5 |
|
6 |
$profile = new Profile(); |
7 |
$profile->CreateOrUpdate( //inputs ); |
Si vous deviez exécuter ce code ailleurs, ou l'utiliser sur un autre projet, vous devriez tout ré-écrire entièrement une nouvelle fois.
Une meilleure solution
En complète opposition avec ce qui suit :
1 |
|
2 |
<?php
|
3 |
$account_domain = new Account(); |
4 |
$account_domain->NewAccount( // entrées ); |
Dans cette situation, nous avons une classe "d'enrobage" (wrapper), qui pourrait bien être la classe principale Account :
1 |
|
2 |
<?php
|
3 |
class Account() |
4 |
{
|
5 |
public function NewAccount( // entrées ) |
6 |
{
|
7 |
$user = new User(); |
8 |
$user->CreateOrUpdate( // sous-ensemble des entrées ); |
9 |
|
10 |
$profile = new Profile(); |
11 |
$profile->CreateOrUpdate( // sous-ensemble des entrées ); |
12 |
}
|
13 |
}
|
De cette façon, vous pouvez vous servir de la classe Account à tout instant, selon vos besoins. Par ailleurs, vous pourriez encapsuler d'autres classes sous celle-ci.
Le Motif Fonctionnel d'Usine (Factory Method Pattern)

Le Motif Fonctionnel d'Usine (Factory Method Pattern) fait partie de la famille des Motif de Conception constructifs, qui fait exactement ce qu'il prétend : une classe agit comme une "usine" à instanciation d'objets.
Son principal objectif est d'encapsuler une procédure de construction, qui peut couvrir différentes classes en une seule et unique fonction. En déterminant le contexte idéal de cette méthode d'"usinage", il sera en mesure de renvoyer l'objet attendu.
Quand dois-je l'utiliser ?

Le meilleur moment pour développer un Motif Fonctionnel d'Usinage apparaît dèse vous rencontrez des variations multiples et singulières d'une entité seule. Disons que vous avez une classe Button ; cette classe comporte différents états, comme ImageButton (bouton en image), InputButton et FlashButton. Selon le contexte, vous avez intérêt à créer tous ces types de boutons. Voici l'instant idéal pour introduire une classe Usine (Factory) pour vous faciliter le travail !
Commençons par créer trois classes :
1 |
|
2 |
<?php
|
3 |
abstract class Button { |
4 |
protected $_html; |
5 |
|
6 |
public function getHtml() |
7 |
{
|
8 |
return $this->_html; |
9 |
}
|
10 |
}
|
11 |
|
12 |
class ImageButton extends Button { |
13 |
protected $_html = "..."; // Insérez n'importe quel code HTML comportant un tag de type IMG. |
14 |
}
|
15 |
|
16 |
class InputButton extends Button { |
17 |
protected $_html = "..."; // Insérez n'importe quel code HTML pour votre bouton normal. |
18 |
(<input type="button"... />); |
19 |
}
|
20 |
|
21 |
class FlashButton extends Button { |
22 |
protected $_html = "..."; // Insérez n'importe quel code HTML pour votre bouton de type "Flash". |
23 |
}
|
Maintenant, créons notre classe Usine (Factory) :
1 |
|
2 |
<?php
|
3 |
class ButtonFactory |
4 |
{
|
5 |
public static function createButton($type) |
6 |
{
|
7 |
$baseClass = 'Button'; |
8 |
$targetClass = ucfirst($type).$baseClass; |
9 |
|
10 |
if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) { |
11 |
return new $targetClass; |
12 |
} else { |
13 |
throw new Exception("The button type '$type' is not recognized."); |
14 |
}
|
15 |
}
|
16 |
}
|
Nous pourrions utiliser ce code comme suit :
1 |
|
2 |
$buttons = array('image','input','flash'); |
3 |
foreach($buttons as $b) { |
4 |
echo ButtonFactory::createButton($b)->getHtml() |
5 |
}
|
Le code résultant de ces classes sera le code HTML pour tous vos types de boutons. Par ce biais, vous serez capable de préciser quel bouton à créer selon la situation et ré-utiliser aussi sa condition.
Le Motif de Décoration (Decorator Pattern)

Le Décorateur fait partie des Motifs de Conception structurels qui permettent l'ajout ou la création de comportements supplémentaires à un objet lors de son exécution, selon la situation.
Le but est de faire en sorte que des fonctions supplémentaires s'appliquent à une instance particulière tout en étant capable de créer une instance originelle ne les contenant pas. Il permet aussi de combiner plusieurs décorateurs à une seule instance, afin de ne pas être coincé dans un seul décorateur à chaque instance. Cette méthode de programmation est une alternative aux sous-classes, référence à la création de classes héritant des fonctionnalités de la classe parente. A l'opposé même de cette pratique, incorporant ce comportement au moment de la compilation du code, la "décoration" permet d'injecter de nouvelles actions en cours d'exécution, si la situation le nécessite.
Pour implémenter le Décorateur, suivez ces étapes :
- Sous-classez le "Composant" original en classe "Decorator"
- Au sein de la classe "Decorator", ajoutez une variable au Component
- Passez le Componant au constructeur du Decorator pour démarrer son instantiation
- Dans la classe du Decorator, redirigez toutes les méthodes du "Component" vers ses attributs, et
- Dans la classe Decorator, outrepassez n'importe quelle méthode du Component dont le comportement doit être différent
Extraits de http://fr.wikipedia.org/wiki/Décorateur_(patron_de_conception)
Quand dois-je l'utiliser ?

L'endroit idéal où le Décorateur prend tout son sens devient évident dès que vous développez une entité qui nécessite un nouveau comportement, uniquement quand le contexte le nécessite. Disons que vous avez un élément comportant un lien HTML, comme un hyperlien pour se déconnecter. Il doit se comporter différemment selon la page visée. Pour ça, rien de tel qu'un Motif de Décoration !
D'abord, établissons les différentes "décorations" dont nous avons besoin.
- Si nous sommes sur la page d'accueil et connecté : ce lien doit être recouvert par une balise h2
- Si nous sommes sur une autre page et connecté : ce lien doit être recouvert par une balise underline
- Si, de toute façon, nous sommes connecté : ce lien doit être nécessairement recouvert par une balise strong
Une fois ces règles établies, commençons à les coder.
1 |
|
2 |
<?php
|
3 |
class HtmlLinks { |
4 |
// Quelques méthodes disponibles pour tous les liens HTML
|
5 |
}
|
6 |
|
7 |
class LogoutLink extends HtmlLinks { |
8 |
protected $_html; |
9 |
|
10 |
public function __construct() { |
11 |
$this->_html = "<a href=\"logout.php\">Logout</a>"; |
12 |
}
|
13 |
|
14 |
public function setHtml($html) |
15 |
{
|
16 |
$this->_html = $html; |
17 |
}
|
18 |
|
19 |
public function render() |
20 |
{
|
21 |
echo $this->_html; |
22 |
}
|
23 |
}
|
24 |
|
25 |
class LogoutLinkH2Decorator extends HtmlLinks { |
26 |
protected $_logout_link; |
27 |
|
28 |
public function __construct( $logout_link ) |
29 |
{
|
30 |
$this->_logout_link = $logout_link; |
31 |
$this->setHtml("<h2>" . $this->_html . "</h2>"); |
32 |
}
|
33 |
|
34 |
public function __call( $name, $args ) |
35 |
{
|
36 |
$this->_logout_link->$name($args[0]); |
37 |
}
|
38 |
}
|
39 |
|
40 |
class LogoutLinkUnderlineDecorator extends HtmlLinks { |
41 |
protected $_logout_link; |
42 |
|
43 |
public function __construct( $logout_link ) |
44 |
{
|
45 |
$this->_logout_link = $logout_link; |
46 |
$this->setHtml("<u>" . $this->_html . "</u>"); |
47 |
}
|
48 |
|
49 |
public function __call( $name, $args ) |
50 |
{
|
51 |
$this->_logout_link->$name($args[0]); |
52 |
}
|
53 |
}
|
54 |
|
55 |
class LogoutLinkStrongDecorator extends HtmlLinks { |
56 |
protected $_logout_link; |
57 |
|
58 |
public function __construct( $logout_link ) |
59 |
{
|
60 |
$this->_logout_link = $logout_link; |
61 |
$this->setHtml("<strong>" . $this->_html . "</strong>"); |
62 |
}
|
63 |
|
64 |
public function __call( $name, $args ) |
65 |
{
|
66 |
$this->_logout_link->$name($args[0]); |
67 |
}
|
68 |
}
|
Nous pourrions les utiliser ainsi :
1 |
|
2 |
$logout_link = new LogoutLink(); |
3 |
|
4 |
if( $is_logged_in ) { |
5 |
$logout_link = new LogoutLinkStrongDecorator($logout_link); |
6 |
}
|
7 |
|
8 |
if( $in_home_page ) { |
9 |
$logout_link = new LogoutLinkH2Decorator($logout_link); |
10 |
} else { |
11 |
$logout_link = new LogoutLinkUnderlineDecorator($logout_link); |
12 |
}
|
13 |
$logout_link->render(); |
Nous pouvons noter ici que nous pouvons exploiter plusieurs décorateurs à volonté. Alors que tous les décorateurs utilisent la méthode magique __call, nous pouvons encore appeler les méthodes de la classe originale. En partant du principe que nous sommes bien sur la page d'accueil et connecté, le code HTML résultant est le suivant :
1 |
|
2 |
<strong><h2><a href="logout.php">Logout</a></h2></strong> |
Le Motif "Singleton" (Singleton Pattern)

Le Motif Singleton appartient à la famille des Motifs de Conception constructifs, et assure qu'il n'y aura qu'une seule instance d'une classe particulière au cours de son exécution, et dispense un point d'accès global à cette seule instance.
Ce motif rend plus facile la mise en œuvre d'un point de "coordination" pour les autres objets dépendants aussi de cette instance "Singleton", car ses variables seront toujours les mêmes pour ceux qui les réclament.
Quand dois-je l'utiliser ?

Si vous devez transmettre une instance particulière d'une classe à une autre, l'utilisation d'une classe Singleton permet d'éviter d'envoyer l'instance via un constructeur ou un argument. Considérons que vous devez créer une classe Session, qui simulerait la variable globale $_SESSION. Dans la mesure où cette classe sera instanciée une seule fois, nous pouvons écrire un motif Singleton comme suit :
1 |
|
2 |
<?php |
3 |
class Session |
4 |
{
|
5 |
private static $instance; |
6 |
|
7 |
public static function getInstance() |
8 |
{
|
9 |
if( is_null(self::$instance) ) { |
10 |
self::$instance = new self(); |
11 |
}
|
12 |
return self::$instance; |
13 |
}
|
14 |
|
15 |
private function __construct() { } |
16 |
|
17 |
private function __clone() { } |
18 |
|
19 |
// any other session methods we might use |
20 |
...
|
21 |
...
|
22 |
...
|
23 |
}
|
24 |
|
25 |
// Démarrer une instance de Session |
26 |
$session = Session::getInstance(); |
Par ce biais, nous avons accès à cette instance de session à différents endroits de notre code, même dans différentes classes. Cette donnée persistera tout au long des appels getInstance.
Conclusion
Il y a encore beaucoup de Motifs de Conception à étudier. Dans cet article, je n'ai mis en lumière que quelques unes des plus pertinents, que j'utilise dans mon code. Si vous êtes curieux d'en connaître davantage sur ces Design Patterns, la page Wikipedia Patron de Conception contient pléthore d'informations sur le sujet. Pour creuser davantage cette question, je vous invite à consulter Design Patterns : Catalogue de modèles de conceptions réutilisables, considéré comme l'ouvrage de référence disponible (version française).
Encore une dernière chose : lors de la mise en œuvre des Motifs de Conception, assurez-vous toujours qu'elles vont vous apporter la meilleure solution à un problème précis. Comme j'ai pu vous le faire remarquer déjà, elles présentent un double tranchant : utiliser dans un contexte inapproprié, elles vont rendre les choses bien trop complexes. Par contre, dans un usage approprié, elles deviennent indispensables.