Les modèles de conception sont réservés aux architectes Java - du moins c'est ce que vous avez toujours pensé. En fait, les modèles de conception sont utiles à tout le monde. Si ces outils ne sont pas l’apanage des « astronautes architecturaux », alors que sont-ils ? Pourquoi sont-ils utiles dans les applications PHP ? Cet article explique ces problèmes.
Le livre Design Patterns a présenté les modèles de conception à la communauté des logiciels. Les auteurs du livre sont Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides Design (communément appelé « Gang of Four »). Les concepts de base derrière les modèles de conception présentés sont très simples. Après des années de pratique du développement de logiciels, Gamma et d'autres ont découvert certains modèles avec des conceptions fixes, un peu comme les architectes conçoivent des maisons et des bâtiments, développant des modèles indiquant l'emplacement d'une salle de bains ou la manière dont une cuisine doit être construite. L’utilisation de ces modèles, ou modèles de conception, permet de concevoir plus rapidement de meilleurs bâtiments. Le même concept s'applique aux logiciels.
Les modèles de conception représentent non seulement un moyen utile de développer plus rapidement des logiciels robustes, mais ils fournissent également un moyen d’encapsuler de grandes idées en termes conviviaux. Par exemple, vous pourriez dire que vous écrivez un système de messagerie qui fournit un couplage lâche, ou vous pourriez dire que vous écrivez un modèle nommé Observer.
Il est très difficile de démontrer la valeur des modèles avec des exemples plus petits. Cela semble souvent exagéré, car les modèles fonctionnent en réalité dans de grandes bases de code. Cet article ne présente pas une application de grande envergure, vous devez donc réfléchir aux moyens d'appliquer les principes de l'exemple dans votre propre application de grande envergure, et non au code lui-même présenté dans cet article. Cela ne veut pas dire que vous ne devriez pas utiliser de modèles dans les petites applications. De nombreuses bonnes applications commencent comme de petites applications et évoluent vers de grandes applications. Il n'y a donc aucune raison de ne pas s'appuyer sur ces types de pratiques de codage solides. Maintenant que vous comprenez les modèles de conception et pourquoi ils sont utiles, examinons les cinq modèles couramment utilisés dans PHP V5.
Modèle d'usine
À l'origine dans le livre Design Patterns, de nombreux modèles de conception encouragent l'utilisation de couplages lâches. Pour comprendre ce concept, il est préférable de parler du parcours ardu que traversent de nombreux développeurs en travaillant sur de grands systèmes. Lorsque vous modifiez un morceau de code, des problèmes peuvent survenir et des ruptures en cascade peuvent se produire dans d’autres parties du système – des parties que vous pensiez auparavant n’avoir aucun rapport.
Le problème est un couplage serré. Les fonctions et classes d'une partie du système dépendent fortement du comportement et de la structure des fonctions et des classes d'autres parties du système. Vous voulez un ensemble de modèles qui permettent à ces classes de communiquer entre elles, mais vous ne voulez pas les lier étroitement pour éviter les imbrications. Dans les grands systèmes, une grande partie du code dépend de quelques classes clés. Des difficultés peuvent survenir lorsqu'il faut modifier ces classes. Par exemple, supposons que vous ayez une classe User qui lit un fichier. Vous souhaitez le remplacer par une classe différente qui lit à partir de la base de données. Cependant, tout votre code fait référence à la classe d'origine qui lit à partir du fichier. À ce stade, il sera très pratique d’utiliser le mode usine.
Le modèle d'usine est une classe qui possède certaines méthodes qui créent des objets pour vous. Vous pouvez utiliser des classes d'usine pour créer des objets sans utiliser directement new. De cette façon, si vous souhaitez changer le type d’objet créé, il vous suffit de changer d’usine. Tout le code utilisant cette usine est automatiquement modifié.
Le listing 1 montre un exemple de classe d'usine. Le côté serveur de l'équation se compose de deux parties : une base de données et un ensemble de pages PHP qui vous permettent d'ajouter des commentaires, de demander une liste de commentaires et d'obtenir des articles liés à un retour spécifique.
Liste 1. Factory1.php
<?php interface IUtilisateur { fonction getName(); }
l'utilisateur de classe implémente IUser { fonction publique __construct( $id ) { }
fonction publique getName() { renvoie "Jack" ; } }
classe UserFactory { fonction statique publique Create( $id ) { renvoyer un nouvel utilisateur( $id ); } }
$uo = UserFactory::Create( 1 ); echo( $uo->getName()."n" ); ?> |
L'interface IUser définit les actions qu'un objet utilisateur doit effectuer. L'implémentation de IUser est appelée User et la classe d'usine UserFactory crée des objets IUser. Cette relation peut être représentée par UML dans la figure 1.
Figure 1. Classe Factory, interface IUser et classe d'utilisateurs associées |
Si vous exécutez ce code sur la ligne de commande à l'aide de l'interpréteur php, vous obtiendrez les résultats suivants :
Le code de test demandera l'objet User à l'usine et affichera le résultat de la méthode getName.
Il existe une variante du modèle d'usine qui utilise des méthodes d'usine. Ces méthodes statiques publiques dans une classe construisent des objets de ce type. Cette méthode est utile s'il est important de créer des objets de ce type. Par exemple, supposons que vous deviez créer un objet, puis définir un certain nombre de propriétés. Cette version du modèle d'usine encapsule le processus dans un seul emplacement, vous n'avez donc pas besoin de copier un code d'initialisation complexe et de le coller partout dans la base de code. Le listing 2 montre un exemple d'utilisation d'une méthode d'usine.
Liste 2. Factory2.php
<?php interface IUtilisateur { fonction getName(); }
l'utilisateur de classe implémente IUser { fonction statique publique Load( $id ) { renvoyer un nouvel utilisateur( $id ); }
fonction statique publique Create( ) { renvoyer un nouvel utilisateur (null); }
fonction publique __construct( $id ) { }
fonction publique getName() { renvoie "Jack" ; } }
$uo = Utilisateur :: Charger ( 1 ); echo( $uo->getName()."n" ); ?> |
Ce code est beaucoup plus simple. Il n'a qu'une seule interface IUser et une classe User qui implémente cette interface. La classe User dispose de deux méthodes statiques pour créer des objets. Cette relation peut être représentée par UML dans la figure 2.
Figure 2. Interface IUser et classe d'utilisateurs avec méthode d'usine |
L'exécution du script sur la ligne de commande produit les mêmes résultats que le listing 1, comme suit :
Comme mentionné ci-dessus, ces modes peuvent parfois sembler excessifs dans des environnements plus petits. Cependant, il est préférable d’apprendre cette forme solide de codage que vous pouvez appliquer à des projets de toute taille.
Mode élément unique
Certaines ressources applicatives sont exclusives car il n’existe qu’une seule ressource de ce type. Par exemple, les connexions à une base de données via un handle de base de données sont exclusives. Vous souhaitez partager le handle de base de données dans votre application car cela représente une surcharge lors du maintien de la connexion ouverte ou fermée, encore plus pendant le processus de récupération d'une seule page.
Le mode élément unique satisfait à cette exigence. Si l’application contient un et un seul objet à la fois, alors cet objet est un singleton. Le code du listing 3 montre un élément unique de connexion à une base de données en PHP V5.
Liste 3. Singleton.php
<?php require_once("DB.php");
classe Base de donnéesConnexion { fonction statique publique get() { statique $db = nul ; si ( $db == null ) $db = new DatabaseConnection(); retourner $db ; }
privé $_handle = null ; fonction privée __construct() { $dsn = 'mysql://root:password@localhost/photos'; $this->_handle =& DB::Connect( $dsn, array() ); }
handle de fonction publique () { retourner $this->_handle ; } }
print( "Handle = ".DatabaseConnection::get()->handle()."n" ); print( "Handle = ".DatabaseConnection::get()->handle()."n" ); ?> |
Ce code montre une seule classe nommée DatabaseConnection. Vous ne pouvez pas créer votre propre DatabaseConnection car le constructeur est privé. Mais en utilisant la méthode get statique, vous pouvez obtenir et obtenir un seul objet DatabaseConnection. L'UML de ce code est présenté dans la figure 3.
Figure 3. Élément unique de connexion à la base de données |
La meilleure preuve est que le handle de base de données renvoyé par la méthode handle est le même entre les deux appels. Vous pouvez exécuter le code dans la ligne de commande pour observer cela.
% php singleton.php Handle = ID d'objet n° 3 Handle = ID d'objet n° 3 % |
Les deux handles renvoyés sont le même objet. Si vous utilisez un élément unique de connexion à la base de données dans toute votre application, vous pouvez réutiliser le même handle partout.
Vous pouvez utiliser des variables globales pour stocker les descripteurs de base de données. Toutefois, cette approche ne convient qu'aux petites applications. Dans les applications plus volumineuses, évitez d’utiliser des variables globales et utilisez des objets et des méthodes pour accéder aux ressources. Modèle d'observateur
Le modèle Observer vous offre un autre moyen d’éviter un couplage étroit entre les composants. Le schéma est très simple : un objet se rend observable en ajoutant une méthode qui permet à un autre objet, l'observateur, de s'enregistrer. Lorsqu'un objet observable change, il envoie des messages aux observateurs enregistrés. Ces observateurs utilisent ces informations pour effectuer des opérations indépendantes de l'objet observable. Le résultat est que les objets peuvent communiquer entre eux sans avoir à comprendre pourquoi. Un exemple simple est une liste d'utilisateurs dans le système. Le code du listing 4 affiche une liste d'utilisateurs et lorsqu'un utilisateur est ajouté, il envoie un message. Cette liste peut être observée via un observateur de journal qui envoie des messages lorsqu'un utilisateur est ajouté.
Liste 4. Observer.php
<?php interface IObservateur { fonction onChanged( $sender, $args ); }
interface IObservable { fonction addObserver( $observer ); }
la classe UserList implémente IObservable { privé $_observers = tableau();
fonction publique addCustomer( $name ) { foreach( $this->_observers comme $obs ) $obs->onChanged( $this, $name ); }
fonction publique addObserver( $observer ) { $this->_observers []= $observer; } }
la classe UserListLogger implémente IObserver { fonction publique onChanged( $sender, $args ) { echo( "'$args' ajouté à la liste des utilisateursn" ); } }
$ul = new UserList(); $ul->addObserver( new UserListLogger() ); $ul->addCustomer( "Jack" ); ?> |
Ce code définit quatre éléments : deux interfaces et deux classes. L'interface IObservable définit les objets qui peuvent être observés, et UserList implémente cette interface afin de s'enregistrer comme observable. La liste IObserver définit comment devenir un observateur. UserListLogger implémente l'interface IObserver. Ces éléments sont représentés dans l'UML dans la figure 4.
Figure 4. Liste d'utilisateurs observables et enregistreur d'événements de liste d'utilisateurs |
Si vous l'exécutez depuis la ligne de commande, vous verrez le résultat suivant :
% php observateur.php 'Jack' ajouté à la liste des utilisateurs % |
Le code de test crée une UserList et y ajoute l'observateur UserListLogger. Ajoutez ensuite un consommateur et informez le UserListLogger de ce changement.
Il est essentiel de réaliser que UserList ne sait pas ce que fera l'enregistreur. Il peut y avoir un ou plusieurs écouteurs qui effectuent d'autres opérations. Par exemple, vous pourriez avoir un observateur qui envoie un message aux nouveaux utilisateurs pour leur souhaiter la bienvenue dans le système. L'intérêt de cette approche est que UserList ignore tous les objets qui en dépendent et se concentre principalement sur la maintenance de la liste des utilisateurs et l'envoi de messages lorsque la liste change.
Ce modèle ne se limite pas aux objets en mémoire. C'est la base des systèmes de requête de messages basés sur des bases de données utilisés dans des applications plus importantes. mode chaîne de commandement
Le modèle de chaîne de commande est basé sur des sujets faiblement couplés, envoyant des messages, des commandes, des requêtes ou toute autre chose via un ensemble de gestionnaires. Chaque gestionnaire juge lui-même s'il peut traiter la demande. Si c'est le cas, la demande est traitée et le processus s'arrête. Vous pouvez ajouter ou supprimer des gestionnaires du système sans affecter les autres gestionnaires. Le listing 5 montre un exemple de ce modèle.
Liste 5. Chain.php
<?php interfaceICommand { fonction onCommand( $name, $args ); }
classe CommandChain { privé $_commands = tableau();
fonction publique addCommand( $cmd ) { $this->_commands []= $cmd; }
fonction publique runCommand( $name, $args ) { foreach( $this->_commands comme $cmd ) { if ( $cmd->onCommand( $name, $args ) ) retour; } } }
la classe UserCommand implémente ICommand { fonction publique onCommand( $name, $args ) { if ( $name != 'addUser' ) renvoie false ; echo( "UserCommand gérant 'addUser'n" ); renvoie vrai ; } }
la classe MailCommand implémente ICommand { fonction publique onCommand( $name, $args ) { if ( $name != 'mail' ) renvoie false ; echo( "MailCommand gère 'mail'n" ); renvoie vrai ; } }
$cc = nouveau CommandChain(); $cc->addCommand( new UserCommand() ); $cc->addCommand( new MailCommand() ); $cc->runCommand( 'addUser', null ); $cc->runCommand( 'mail', null ); ?> |
Ce code définit la classe CommandChain qui gère une liste d'objets ICommand. Les deux classes peuvent implémenter l'interface ICommand : l'une qui répond aux demandes de courrier et l'autre qui répond à l'ajout d'utilisateurs. La figure 5 montre l'UML.
Figure 5. Chaîne de commandes et ses commandes associées |
Si vous exécutez un script contenant du code de test, vous obtenez le résultat suivant :
% php chaîne.php UserCommand gérant 'addUser' MailCommand gérant le « courrier » % |
Le code crée d’abord l’objet CommandChain et y ajoute deux instances de l’objet command. Exécutez ensuite deux commandes pour voir qui a répondu aux commandes. Si le nom de la commande correspond à UserCommand ou MailCommand, le code échoue et aucune action ne se produit. Le modèle de chaîne de commande est précieux lors de la création d’une architecture évolutive pour le traitement des demandes, et de nombreux problèmes peuvent être résolus en l’utilisant. modèle de stratégie
Le dernier modèle de conception que nous abordons est le modèle de stratégie. Dans ce modèle, les algorithmes sont extraits de classes complexes et peuvent donc être facilement remplacés. Par exemple, si vous souhaitez modifier la façon dont les pages sont classées dans les moteurs de recherche, le mode Stratégie est un bon choix. Pensez aux éléments d'un moteur de recherche : un qui parcourt les pages, un qui classe chaque page et un autre qui trie les résultats en fonction du classement. Dans les exemples complexes, ces pièces appartiennent toutes à la même classe. En utilisant le modèle Strategy, vous pouvez placer la partie arrangement dans une autre classe pour modifier la façon dont la page est organisée sans affecter le reste du code du moteur de recherche.
À titre d'exemple plus simple, le listing 6 montre une classe de liste d'utilisateurs qui fournit un moyen de trouver un ensemble d'utilisateurs basé sur un ensemble de politiques plug-and-play.
Liste 6. Strategy.php
<?php interface ISratégie { fonction filtre( $record ); }
la classe FindAfterStrategy implémente IStrategy { privé $_name ;
fonction publique __construct( $name ) { $this->_name = $nom; }
filtre de fonction publique ($ record) { return strcmp( $this->_name, $record ) <= 0; } }
la classe RandomStrategy implémente IStrategy { filtre de fonction publique ($ record) { return rand( 0, 1 ) >= 0,5; } }
classe Liste d'utilisateurs { privé $_list = tableau();
fonction publique __construct( $names ) { si ( $noms != null ) { foreach( $names comme $name ) { $this->_list []= $nom ; } } }
fonction publique ajouter( $name ) { $this->_list []= $nom ; }
fonction publique find( $filter ) { $recs = tableau(); foreach( $this->_list en tant que $user ) { si ( $filter->filter( $user ) ) $recs []= $utilisateur ; } renvoie $recs ; } }
$ul = new UserList( array( "Andy", "Jack", "Lori", "Megan" ) ); $f1 = $ul->find( new FindAfterStrategy( "J" ) ); print_r( $f1 );
$f2 = $ul->find( new RandomStrategy() ); print_r( $f2 ); ?> |
Figure 6. Liste d'utilisateurs et stratégie utilisée pour sélectionner les utilisateurs |
La classe UserList est un wrapper autour d'un tableau de noms. Il implémente la méthode find, qui utilise l'une des nombreuses stratégies pour sélectionner un sous-ensemble de ces noms. Ces stratégies sont définies par l'interface IStrategy, qui a deux implémentations : une qui sélectionne aléatoirement l'utilisateur et une autre qui sélectionne tous les noms après le nom spécifié. Lorsque vous exécutez le code de test, vous obtenez le résultat suivant :
%php stratégie.php Tableau ( [0] => Jacques [1] =>Lori [2] =>Mégane ) Tableau ( [0] => Andy [1] =>Mégane ) % |
Le code de test exécute la même liste d'utilisateurs pour les deux stratégies et affiche les résultats. Dans le premier cas, la stratégie recherche n'importe quel nom qui suit J, vous obtiendrez donc Jack, Lori et Megan. La deuxième stratégie sélectionne des noms au hasard, produisant des résultats différents à chaque fois. Dans ce cas, les résultats sont Andy et Megan.
Le modèle Stratégie est idéal pour les systèmes de gestion de données complexes ou les systèmes de traitement de données qui nécessitent un haut degré de flexibilité dans la manière dont les données sont filtrées, recherchées ou traitées.
Conclusion
Cet article présente quelques-uns des modèles de conception les plus couramment utilisés dans les applications PHP. D'autres modèles de conception sont présentés dans le livre Design Patterns. Ne vous laissez pas décourager par la mystique de l’architecture. Les modèles sont une idée merveilleuse qui fonctionne dans n’importe quel langage de programmation et à n’importe quel niveau de compétence.