Les tenants et aboutissants des modèles de conception PHP

De nombreux articles expliquent ce que sont les modèles de conception et comment les mettre en œuvre. le web n'a pas besoin d'un autre de ces articles! Au lieu de cela, dans cet article, nous discuterons davantage de la quand et Pourquoi, plûtot que le lequel et Comment.

Je présenterai différentes situations et différents cas d'utilisation pour les modèles, et fournirai également des définitions courtes pour aider ceux d'entre vous qui ne sont pas si familiers avec ces modèles spécifiques. Commençons.

Tutoriel republié

Toutes les quelques semaines, nous revoyons certains des articles préférés de nos lecteurs tout au long de l'histoire du site. Ce tutoriel a été publié pour la première fois en octobre 2012.

Cet article couvre certaines des différentes Modèles de conception agiles, documenté dans les livres de Robert C. Martin. Ces modèles sont des adaptations modernes des modèles de conception d'origine définis et documentés par La bande des quatre en 1994. Les modèles de Martin présentent une approche beaucoup plus récente des modèles du GoF et fonctionnent mieux avec les techniques et les problèmes de programmation modernes. En fait, environ 15% des modèles originaux ont été remplacés par des modèles plus récents et les modèles restants ont été légèrement modernisés..


Commençons par créer des objets

Utiliser un modèle d'usine

Le modèle d'usine a été inventé pour aider les programmeurs à organiser les informations relatives à la création d'objets. Les objets ont parfois beaucoup de paramètres de constructeur; d'autres fois, ils doivent être renseignés avec les informations par défaut immédiatement après leur création. Ces objets doivent être créés dans des usines, en conservant toutes les informations relatives à leur création et initialisation dans un seul endroit..

Quand Utilisez un modèle d'usine lorsque vous vous trouvez en train d'écrire du code pour rassembler les informations nécessaires à la création d'objets..

Pourquoi: Les usines aident à contenir la logique de la création d’objets à un endroit unique. Ils peuvent également rompre les dépendances pour faciliter le couplage lâche et l'injection de dépendance pour permettre de meilleurs tests.


Trouver les données dont nous avons besoin

Il existe deux modèles fréquemment utilisés pour extraire des informations d'une couche de persistance ou d'une source de données externe..

Le modèle de la passerelle

Ce modèle définit un canal de communication entre une solution de persistance et la logique métier. Pour des applications plus simples, il peut récupérer ou recréer des objets entiers par lui-même, mais la création d’objets incombe aux usines de la plupart des applications complexes. Les passerelles récupèrent et conservent simplement les données brutes.

Quand: Lorsque vous avez besoin de récupérer ou de conserver des informations.

Pourquoi: Il offre une interface publique simple pour les opérations de persistance compliquées. Il encapsule également la connaissance de la persistance et dissocie la logique métier de la logique de persistance.

En fait, le modèle de passerelle n’est qu’une implémentation particulière d’un autre modèle de conception dont nous parlerons dans quelques instants: le modèle d’adaptateur..

Allez avec le proxy

Il y a des moments où vous ne pouvez pas (ou ne voulez pas) exposer la connaissance de la couche de persistance à vos classes professionnelles. Le modèle de proxy est un bon moyen de tromper vos classes d'entreprise en leur faisant croire qu'elles utilisent déjà des objets existants..

Quand: Vous devez extraire des informations d'une couche de persistance ou d'une source externe, mais ne souhaitez pas que votre logique métier le sache..

Pourquoi: Offrir une approche non intrusive pour créer des objets en coulisse. Il ouvre également la possibilité de récupérer ces objets à la volée, paresseusement et de différentes sources.

Un proxy implémente efficacement la même interface en tant qu'objet réel et imite ses fonctionnalités. La logique métier l'utilise simplement comme s'il s'agissait d'un objet réel, mais en réalité, le proxy crée l'objet s'il n'en existe pas..

Le modèle d'objet actif a également joué un rôle dans les premiers systèmes multitâches.

OK OK. C'est génial et tout, mais comment pouvons-nous trouver les objets que nous devons créer?

Demander un référentiel

Le modèle de référentiel est très utile pour la mise en œuvre de méthodes de recherche et de langages de mini-requête. Il prend ces requêtes et utilise une passerelle pour obtenir les données d'une usine pour produire les objets dont vous avez besoin.

Le modèle de référentiel est différent des autres modèles; il fait partie de la conception dirigée par le domaine (DDD) et ne fait pas partie du livre de Robert C. Martin.

Quand: Vous devez créer plusieurs objets en fonction de critères de recherche ou lorsque vous devez enregistrer plusieurs objets sur le calque de persistance..

Pourquoi: Permettre aux clients ayant besoin d'objets spécifiques de travailler avec un langage de requête et de persistance commun et bien isolé. Il supprime encore plus de code lié à la création de la logique métier.

Mais que se passe-t-il si le référentiel ne peut pas trouver les objets? Une option serait de retourner un NUL valeur, mais cela a deux effets secondaires:

  • Il jette un legs refusé si vous essayez d'appeler une méthode sur un tel objet.
  • Il vous oblige à inclure de nombreux contrôles nuls (if (is_null ($ param)) renvoie;) dans votre code.

Une meilleure approche consiste à renvoyer un nul objet.

Le modèle d'objet nul

Un objet null implémente la même interface que vos autres objets, mais les membres de l'objet retournent une valeur neutre. Par exemple, une méthode qui retourne une chaîne retournera une chaîne vide; un autre membre renvoyant une valeur numérique renverrait zéro. Cela vous oblige à implémenter des méthodes qui ne renvoient pas de données significatives, mais vous pouvez utiliser ces objets sans vous soucier de refus d'héritage ou de litter votre code avec des contrôles nuls..

Quand: Vous vérifiez souvent nul ou vous avez refusé les legs.

Pourquoi: Cela peut ajouter de la clarté à votre code et vous obliger à réfléchir davantage au comportement de vos objets.

Il n'est pas inhabituel d'appeler plusieurs méthodes sur un objet avant qu'il puisse faire son travail. Il existe des situations où vous devez préparer un objet après sa création avant de pouvoir vraiment l'utiliser. Cela conduit à une duplication du code lors de la création de ces objets à des endroits différents.

Vous avez besoin du modèle de commande

Quand: Lorsque vous devez effectuer de nombreuses opérations pour préparer les objets à utiliser.

Pourquoi: Pour déplacer la complexité du code consommateur au code créateur.

Cela sonne bien, n'est-ce pas? En fait, c'est très utile dans de nombreuses situations. Le modèle de commande est largement utilisé pour la mise en œuvre des transactions. Si vous ajoutez un simple annuler() méthode à un objet de commande, il peut suivre toutes les transactions annulées qu'il a effectuées et les inverser si nécessaire.

Alors maintenant, vous avez dix objets de commande (ou plus) et vous voulez les exécuter simultanément. Vous pouvez les rassembler dans un objet actif.

L'objet actif

L'objet actif simple et intéressant n'a qu'une responsabilité: conserver une liste d'objets de commande et les exécuter..

Quand: Plusieurs objets similaires doivent être exécutés avec une seule commande.

Pourquoi: Il oblige les clients à exécuter une tâche unique et affecte plusieurs objets..

Un objet actif supprime chaque commande de sa liste après son exécution. ce qui signifie que vous ne pouvez exécuter la commande qu’une seule fois. Voici quelques exemples concrets d'objet actif:

Les modèles de conception sont là pour résoudre les problèmes.

  • Chariot - Exécuter un acheter() commande sur chaque produit les retire du panier.
  • Transactions financières - Regrouper les transactions dans une liste unique et les exécuter avec un simple appel à l'objet actif du gestionnaire de liste supprime les transactions de la file d'attente..

Le modèle d'objet actif a également joué un rôle dans les premiers systèmes multitâches. Chaque objet à l'intérieur d'un objet actif conserverait une référence à l'objet actif. Ils exécuteraient une partie de leur travail, puis se remettraient dans la file d'attente. Même dans les systèmes actuels, vous pouvez utiliser un objet actif pour laisser les autres objets fonctionner pendant que vous attendez une réponse d'une autre application..


Réutilisabilité

Je suis convaincu que vous avez entendu la grande promesse de la programmation orientée objet: la réutilisation de code. Les premiers utilisateurs de OOP ont envisagé d'utiliser des bibliothèques et des classes universelles dans des millions de projets différents. Eh bien, ce n'est jamais arrivé.

Créer des méthodes de modèles à la place

Ce modèle permet la réutilisation partielle du code. C'est pratique avec plusieurs algorithmes qui ne diffèrent que légèrement les uns des autres.

Quand: Éliminer la duplication de manière simple.

Pourquoi: Il y a des doubles emplois et la flexibilité n'est pas un problème.

Mais la flexibilité est agréable. Et si j'en ai vraiment besoin?

C'est le temps d'une stratégie

Quand: La flexibilité et la réutilisabilité sont plus importantes que la simplicité.

Pourquoi: Utilisez-le pour implémenter de gros blocs interchangeables de logique compliquée, tout en conservant une signature d'algorithme commune.

Par exemple, vous pouvez créer un générique Calculatrice et ensuite utiliser différents Stratégie de calcul objets pour effectuer les calculs. C'est un modèle modérément utilisé, et c'est le plus puissant lorsque vous devez définir de nombreux comportements conditionnels.


Capacité de découverte

À mesure que les projets se développent, les utilisateurs externes ont de plus en plus de difficultés à accéder à notre application. C'est une des raisons pour offrir un point d'entrée bien défini à l'application ou au module en question. D'autres raisons peuvent inclure le désir de dissimuler le fonctionnement interne et la structure du module.

Présenter une façade

Une façade est essentiellement une API - une interface conviviale et orientée client. Lorsqu'un client appelle l'une de ces méthodes agréables, la façade délègue une série d'appels aux classes qu'elle cache afin de fournir au client les informations requises ou le résultat souhaité..

Quand: Pour simplifier votre API ou dissimuler intentionnellement une logique métier interne.

Pourquoi: Vous pouvez contrôler l'API et les implémentations réelles et la logique indépendamment.

Le contrôle est bon et vous devez souvent exécuter une tâche lorsque quelque chose change. Les utilisateurs doivent être avertis, les voyants rouges doivent clignoter, une alarme doit sonner… vous voyez l'idée.

Le populaire framework Laravel fait un excellent usage du motif de façade.

S'abonner à un observateur

Un objet null implémente la même interface que vos autres objets.

Le modèle d'observateur offre un moyen simple de surveiller des objets et de prendre des mesures lorsque les conditions changent. Il existe deux types de mises en œuvre d'observateur:

  • Vote - Les objets acceptent les abonnés. Les abonnés observent l'objet et sont avertis d'événements spécifiques. Les abonnés demander les objets observés pour plus d'informations afin de prendre des mesures.
  • Pousser - À l'instar de la méthode d'interrogation, les objets acceptent les abonnés, qui en sont avertis lorsqu'un événement se produit. Mais quand une notification arrive, l'observateur reçoit également un indice que l'observateur peut agir sur.

Quand: Pour fournir un système de notification dans votre logique métier ou au monde extérieur.

Pourquoi: Le modèle offre un moyen de communiquer des événements à un nombre quelconque d'objets différents.

Les cas d'utilisation de ce modèle sont les notifications par courrier électronique, les démons de journalisation ou les systèmes de messagerie. Bien sûr, dans la vie réelle, il existe d'innombrables autres façons de l'utiliser.

Coordonner les effets

Le motif observateur peut être étendu avec un motif médiateur. Ce modèle prend deux objets en tant que paramètres. Le médiateur s'abonne au premier paramètre et lorsqu'un changement survient dans l'objet observé, le médiateur décide quoi faire du deuxième objet..

Quand: Les objets affectés ne peuvent pas connaître les objets observés.

Pourquoi: Pour offrir un mécanisme caché permettant d’affecter d’autres objets du système lorsqu’un objet est modifié.


Singularité

Parfois, vous avez besoin d'objets spéciaux uniques dans votre application et vous voulez vous assurer que tous les consommateurs peuvent voir les modifications apportées à ces objets. Vous souhaitez également empêcher la création de plusieurs instances de tels objets pour certaines raisons, telles qu'un délai d'initialisation long ou des problèmes liés aux actions simultanées sur certaines bibliothèques tierces.

Utilisez un singleton

Un singleton est un objet ayant un constructeur privé et un public getInstance () méthode. Cette méthode garantit qu'il n'existe qu'une seule instance de l'objet..

Quand: Vous devez atteindre la singularité et vouloir une solution multi-plateforme, évaluée paresseusement, qui offre également la possibilité de créer par dérivation..

Pourquoi: Offrir un point d'accès unique en cas de besoin.

Ou écrivez un objet monostate

Une autre approche de la singularité est le modèle de conception monostate. Cette solution utilise une astuce offerte par les langages de programmation orientés objet. Il a des méthodes publiques dynamiques qui obtiennent ou définissent les valeurs de variables privées statiques. Cela garantit à son tour que toutes les instances de ces classes partagent les mêmes valeurs..

Quand: La transparence, la dérivabilité et le polymorphisme sont préférés avec la singularité.

Pourquoi: Cacher aux utilisateurs / clients le fait que l'objet offre une singularité.

Portez une attention particulière à la singularité. Il pollue l'espace de noms global et, dans la plupart des cas, peut être remplacé par quelque chose de mieux adapté à cette situation particulière..


Contrôler différents objets

Le modèle de référentiel est très utile pour la mise en œuvre de méthodes de recherche…

Donc, vous avez un interrupteur et une lumière. Le commutateur peut allumer et éteindre la lumière, mais vous avez maintenant acheté un ventilateur et souhaitez utiliser votre ancien commutateur avec celui-ci. C'est facile à accomplir dans le monde physique; prenez l'interrupteur, connectez les fils et l'alto.

Malheureusement, ce n'est pas si facile dans le monde de la programmation. Tu as un Commutateur classe et un Lumière classe. Si ton Commutateur utilise le Lumière, comment pourrait-il utiliser le Ventilateur?

Facile! Copier et coller le Commutateur, et le changer pour utiliser le Ventilateur. Mais c'est la duplication de code; c'est l'équivalent d'acheter un autre commutateur pour le ventilateur. Vous pourriez prolonger Commutateur à FanSwitch, et utiliser cet objet à la place. Mais si vous voulez utiliser un Bouton ou Télécommande, au lieu d'une Commutateur?

Le modèle de serveur abstrait

C'est le motif le plus simple jamais inventé. Il utilise uniquement une interface. C'est ça, mais il y a plusieurs implémentations différentes.

Quand: Vous devez connecter des objets et maintenir la flexibilité.

Pourquoi: Parce qu’il s’agit du moyen le plus simple d’atteindre la flexibilité tout en respectant le principe de l’inversion de dépendance et le principe de fermeture.

PHP est typé dynamiquement. Cela signifie que vous pouvez omettre des interfaces et utiliser différents objets dans le même contexte, risquant ainsi un legs refusé. Cependant, PHP permet également la définition d'interfaces et je vous recommande d'utiliser cette fonctionnalité géniale pour clarifier l'intention de votre code source..

Mais vous avez déjà plusieurs cours auxquels vous voulez parler? Oui bien sûr. Il existe de nombreuses bibliothèques, API tierces et autres modules auxquels on doit parler, mais cela ne signifie pas que notre logique métier doit connaître les détails de telles choses..

Branchez un adaptateur

Le modèle d'adaptateur crée simplement une correspondance entre la logique métier et autre chose. Nous avons déjà vu un tel modèle en action: le modèle de passerelle.

Quand: Vous devez créer une connexion avec un module, une bibliothèque ou une API préexistante et potentiellement changeante..

Pourquoi: Pour permettre à votre logique métier de ne s'appuyer que sur les méthodes publiques proposées par l'adaptateur et de changer facilement l'autre côté de l'adaptateur.

Si l’un des modèles ci-dessus ne correspond pas à votre situation, vous pouvez utiliser…

Le modèle de pont

C'est un modèle très compliqué. Personnellement, je n'aime pas cela parce qu'il est généralement plus facile d'adopter une approche différente. Mais dans ces cas particuliers, lorsque d’autres solutions échouent, vous pouvez envisager le modèle de pont..

Quand: Le modèle d'adaptateur ne suffit pas et vous modifiez les classes des deux côtés du tuyau.

Pourquoi: Offrir une flexibilité accrue au prix d'une complexité importante.


Le motif composite

Considérez que vous avez un script avec des commandes similaires et que vous voulez effectuer un seul appel pour les exécuter. Attendre! N'avons-nous pas déjà vu quelque chose comme ça plus tôt? Le modèle d'objet actif?

Oui, oui nous l'avons fait. Mais celui-ci est un peu différent. C'est le modèle composite et, comme le modèle d'objet actif, il conserve une liste d'objets. Mais appeler une méthode sur un objet composite appelle la même méthode sur tous ses objets sans les supprimer de la liste. Les clients appelant une méthode pensent qu'ils parlent à un seul objet de ce type particulier, mais en réalité, leurs actions sont appliquées à de très nombreux objets du même type..

Quand: Vous devez appliquer une action à plusieurs objets similaires.

Pourquoi: Pour réduire les doublons et simplifier la manière dont des objets similaires sont appelés.

Voici un exemple: vous avez une application capable de créer et de placer des Ordres. Supposons que vous ayez trois commandes: $ order1, $ order2 et $ order3. Vous pouvez appeler endroit() sur chacun d’eux, ou vous pouvez contenir ces ordres dans un $ compositeOrder objet, et appeler son endroit() méthode. Ceci, à son tour, appelle le endroit() méthode sur tous les contenus Ordre objets.


Le modèle d'état

Les passerelles récupèrent et conservent uniquement les données brutes.

Une machine à états finis (FSM) est un modèle qui possède un nombre fini d'états discrets. La mise en place d’un FSM peut être difficile, et le moyen le plus simple de le faire implique la confiance commutateur déclaration. Chaque Cas instruction représente un état actuel de la machine et sait comment activer l'état suivant.

Mais nous savons tous que commutateur… cas les déclarations sont moins souhaitables car elles produisent une grande fan-out indésirable sur nos objets. Alors oublie le commutateur… cas déclaration, et à la place, considérez le modèle d'état. Le modèle d'état est composé de plusieurs objets: un objet pour coordonner les objets, une interface représentant un état abstrait, puis plusieurs implémentations, une pour chaque état. Chaque état sait quel état vient après, et l'état peut notifier à l'objet de coordination de définir son nouvel état sur le suivant..

Quand: Une logique de type FSM doit être implémentée.

Pourquoi: Pour éliminer les problèmes d'un commutateur… cas déclaration, et pour mieux encapsuler le sens de chaque état individuel.

Un distributeur de nourriture pourrait avoir un principale classe qui a une référence à un Etat classe. Les classes d'état possibles pourraient être quelque chose comme: WaitingForCoin, InsertedCoin, Produit sélectionné, En attente de confirmation, LivrerProduit, RetourChange. Chaque état effectue son travail et crée le prochain objet d'état à envoyer à la classe de coordinateur..


Décorer avec le motif de décorateur

Il arrive que vous déployez des classes ou des modules dans une application et que vous ne pouvez pas les modifier sans affecter radicalement le système. Mais, en même temps, vous devez ajouter les nouvelles fonctionnalités dont vos utilisateurs ont besoin..

Le motif de décorateur peut aider dans ces situations. C'est très simple: prenez les fonctionnalités existantes et ajoutez-les. Ceci est accompli en étendant la classe d'origine et en fournissant une nouvelle fonctionnalité au moment de l'exécution. Les anciens clients continuent à utiliser le nouvel objet comme ils le feraient auparavant, et les nouveaux clients utiliseront à la fois l'ancienne et la nouvelle fonctionnalité..

Quand: Vous ne pouvez pas changer les anciennes classes, mais vous devez implémenter un nouveau comportement ou un nouvel état.

Pourquoi: Il offre un moyen peu intrusif d'ajouter de nouvelles fonctionnalités.

Un exemple simple est l’impression de données. Vous imprimez certaines informations à l'utilisateur sous forme de texte brut, mais vous souhaitez également pouvoir imprimer en HTML. Le motif décoratif est une solution qui vous permet de conserver les deux fonctionnalités.

Ou accepter un visiteur

Si votre problème d'extension des fonctionnalités est différent (par exemple, vous avez une structure complexe en forme d'arborescence d'objets et que vous souhaitez ajouter des fonctionnalités à plusieurs nœuds à la fois), une simple itération est impossible, mais un visiteur peut constituer une solution viable. L’inconvénient, c’est que l’implémentation d’un modèle de visiteur nécessite une modification de l’ancienne classe si elle n’était pas conçue pour accepter un visiteur..

Quand: Un décorateur n'est pas approprié et une complexité supplémentaire est acceptable.

Pourquoi: Permettre une approche organisée pour définir la fonctionnalité de plusieurs objets, mais au prix d’une complexité accrue.


Conclusion

Utilisez des modèles de conception pour résoudre vos problèmes, mais uniquement s'ils conviennent.

Les modèles de conception aident à résoudre les problèmes. En tant que recommandation d'implémentation, ne nommez jamais vos classes après les modèles. Au lieu de cela, trouvez les bons noms pour les bonnes abstractions. Cela vous aide à mieux discerner quand vous avez vraiment besoin d’un modèle plutôt que de simplement en appliquer un, car vous pouvez le faire..

Certains diront que si vous ne nommez pas votre classe avec le nom du modèle, les autres développeurs auront du mal à comprendre votre code. S'il est difficile de reconnaître un modèle, le problème réside dans son implémentation..

Utilisez des modèles de conception pour résoudre vos problèmes, mais uniquement s'ils y correspondent. Ne pas en abuser. Vous constaterez qu'une solution plus simple convient à un petit problème; alors, vous découvrirez que vous avez besoin d'un modèle uniquement après avoir implémenté quelques autres solutions.

Si vous êtes novice dans la conception de motifs, j'espère que cet article vous a donné une idée de l'utilité de ces motifs dans vos applications. Merci d'avoir lu!