Exemples d'implémentation de patrons de conception en php.
Le package est écrit à l'aide de l'analyseur statique de code psalm. Le code utilise un maximum les nouvelles fonctionnalités de typages offertes par php 7.4. Le code est couvert par les tests à 100%.
Les différents diagrames de classe UML sont générés à partir du code. Le package utilisé ne représente pas les relations entre les classes.
The Gang of Four working on their next pattern idea.
- php ^7.4
- composer
Pour générer les diagrames de classe UML:
- extension php imagick
- graphviz installé (+ var d'environnement PATH pour windows)
You can install the package via composer:
git clone
composer update
Attention, le contenu est en construction!
Permet de forcer une classe à n'être instanciée qu'une seule fois. Pour mettre ce patron en oeuvre, on fait appel aux attributs statiques et mettant la visibilité private aux constructeurs.
En php on va faire en sorte que les méthodes magiques __construct(), __clone() et __wakeup() ne puissent pas être utilisées.
Problème: la variable statique $instance est partagée pour toutes les classes qui étendent Singleton
Problème: En php, on a pas encore de types génériques. On peut utiliser Psalm pour les simuler avec la balise "@ template T" Malheureusement, elle ne fonctionne pas pour les attributs statiques.
Solution: On peut stocker les instances dans un tableau.
https://blog.cotten.io/how-to-screw-up-singletons-in-php-3e8c83b63189
Solution: On peut utiliser la réflexion pour recréer des classes en leur ajoutant les fonctionnalités d'un singleton. Exemple de création d'une SingletonFactory.
https://patrick-assoa-adou.medium.com/a-generic-php-singleton-the-long-of-it-661b1ead3981
Solution: Ou on passe par une fonction singletonize
https://patrick-assoa-adou.medium.com/a-generic-php-singleton-1985f17eeb6f
Remarque: C'est sencé être mieux d'utiliser l'injection de dépendances que Singleton que certains considèrent comme un anti-pattern
Usages fréquents:
- DAO (accès db)
- un Logger
- lock file pour l'application
Exemple:
- Laravel ServiceContainer
code de l'exemple du dictionnaire
On peut instancier plusieurs classes qui étendent DictSingleton. On viole les principes du patron Singleton de base.
À quoi ça sert: On est quand même certain que les différents dictiunaires ne seront instanciés qu'une fois et qu'on ne permet pas de modifier ces objets.
Permet de créer des objets différents avec une API qui sera similaire. Exemple ici avec Des usines à voitures thermiques et électriques.
code du pattern AbstractFactory
Utilisation:
- Quand plusieurs lignes de produits à gérer
- Un reader de fichier différent en fonction de l'OS
Cloner des objets plutôt que de les créer par l'opération "new ObjectClass()". Se rélèle moins couteux et demande souvent moins de code quand on a besoin de plusieurs objets similaires. Attention, il faut implémenter la méthode clone pour faire des deepcopies.
Utilisations:
- Interface graphique de création de niveau de jeux video.
- logiciel CAD?
Exercice: Utiliser une classe abstraite qui utilise un array pour stocker les valeurs des attributs et fourni une méthode clone qui copie les attributs
Bonus php: On implémente l'interface ArrayAccess pour accéder au valeurs de la propriété $attributes
d'un de nos objets avec la même notation que celle d'un tableau (Ex: $floor['floorId]
)
Permet de cacher un objet couteux en ressources en ne manipulant qu'un objet proxy qui va s'occuper de créer et manipuler l'objet couteux pour nous.
Utilisations:
- On doit controller l'accès à un objet
- On doit ajouter des fonctionnalités quand on accède à un objet.
- Droits d'accès
- Le proxy peut-être utilisé comme substitue d'un autre objet
Types de proxies:
- Protection proxy (controlle d'accès à une ressource)
- Virtual proxy (exemple du placeholder video)
- Remote proxy (objet local qui cache les appels à distance vers un serveur)
Sources:
https://en.wikipedia.org/wiki/Proxy_pattern
https://www.geeksforgeeks.org/proxy-design-pattern/
Permet d'ajouter dynamiquement des fonctionnalités à un objet. Alternative à la création de sous-classes.
Ils ont une structure proche. Ils utilisent tous deux une interface pour créer des objets aux méthodes compatibles et chainables.
Le but de pattern Proxy est "de représenter"/"être à la place" d'un autre objet, tandis que le but du pattern Decorator est d'ajouter des nouvelles fonctionnalités à des objets à l'exécution.
L'effet 3D s'applique sur la fenetre mais pas sur les scrollbars.
Solution:
- On force une instance de classe à n'être référencée qu'une seule fois. (je suis pas certain d'avoir bien compris 😥)
https://en.wikipedia.org/wiki/Decorator_pattern#Motivation
Exercice: Gestionnaire de fichiers avec le pattern Composite.
- création de fichiers et dossiers
- supprimer des fichiers et dossiers
- déplacer des fichiers et dossiers
- copier des fichiers et dossiers
- check si des fichiers et dossiers sont les même / ont la même structure
- check si deux fichiers ont la même structure
Exercice: Gestionnaire de fichiers avec le pattern Composite.
Solution:
- Ajout d'une classe FileManager qui permet de simplifier l'api
- Quelques méthodes en plus (find($name), goTo($path))
- Attention, l'écriture sur disque n'est pas implémentée. Pour cela, il faudrait juste compléter certaines méthodes.
TODO
Comment utiliser le pattern PlayerRole avec le pattern Composite pour implémenter la transformation d'une feuille/container en component?
TODO
On pourrait utiliser les interface :
- (SplObjectStorage)[https://www.php.net/manual/fr/class.splobjectstorage.php] dans la classe Component pour stocker les enfants
- (RecursiveIterator)[https://www.php.net/manual/fr/class.recursiveiterator.php] Pour naviguer dans les enfants
- (SeekableIterator)[https://www.php.net/manual/fr/class.seekableiterator.php] Pour trouver des enfants (la logique est déjà écrite, il faudrait juste changer les noms de méthodes) ou naviguer dans l'arbre depuis la root dans le FileManager
- Offrir une interface unifiée pour un ensemble d'interface d'un sous système.
- Une facade défini une interface de plus haut niveau qui rend un sous système plus simple à utiliser
- Cacher l'implémentation "sale" de fonctionnalités
Permet de découpler l'implémentation d'une interface et de découpler notre code de l'interface. On peut ainsi changer d'implémentation durant l'exécution.
Exemple: Système de réponse qui peut render de l'Html soit du Json en fonction de l'implémentation de renderer utilisée.
- Pas d'impacte notable. On doit juste créer une nouvelle classe qui implémente RendererImplementation
RendererImplementation est une interface. Comment on peut l'améliorer en utilisant une classe abstraite (voir pattern Template)
- Todo
On pourrait utiliser les abstract factory pour choisir l'implémentation utilisée en fonction des besoins dans chaque factory.
- Permet d'éviter d'avoir une grande quantité d'objets qui se ressemblent en mémoire
Traitement de texte pour lequel on peut appliquer différents styles à du texte. On veut ici éviter d'avoir un objet pour chaque lettre par exemple.
On utilise une factory pour créer les objets Lettre. La factory créera un objet lettre s'il n'est pas encore instancié, sinon il utilisera une référence vers l'objet qui existe déjà. Cela permet aux objets Lettre d'être aggnostiques aux contexte dans lesquels ils sont utilisés (et ainsi réutilisables dans plusieurs contextes différents). Pour ce qui est du style de la lettre, on ira le stocker dans un objet conteneur (par exemple Mot)
Utilisations:
- Quand un changement sur un objet demande de changer d'autres objets
- Quand on doit pouvoir notifier d'autres objets sans que couplage avec ces objets (notification par broadcast)
- Permet de mettre à jour "réactivement" une classe sur base d'un évènement ou si les données d'une autre classe change
- Permet de découper une grosse classe en plusieurs classes qui seront observers de la classe factorisée.
En php, on va utiliser les interfaces SplObserver et SplSubject (inclues dans le langage php) qui sont dédiées à l'implémentation du pattern Observer.
On va aussi utiliser la structure de données dédiée au stockage d'objets SplObjectStorage (inclue dans le langage php) pour enregistrer les observers dans la classe Observed.
- Ajout d'un check dans la méthode setAttribute() de la classe Observed
- on peut regarder quels sont les changements de state si on garde en mémoire l'état précédent dans les observers
Utiliser le pattern Observer avec le pattern State pour que la class soit consciente des transitions d'état de certains objets.
ex: objet x doit être notifié de chagque changement de l'état courant de l'objet y
TODO
-
Ajouter ou enlever des fonctionnalités pendant l'exécution (système de plugin)
-
Modifier des fonctionnalités pendant l'exécution
-
Déclancher des fonctionnalités de facon automatique (installation d'un plugin)
-
- Extensibilité
-
- Flexibilité
-
- Séparation des préoccupations
-
- Réutilisabilité
-
- Effets de bord
-
- Difficulté à gérer les envent rérentrant (ex: message tcp envoyé et recapté par le mécanisme ... boucle infinie)
-
- difficulté d'antaciper les extensions futur du framework
- définir le squelette d'un algorithme pour factoriser plusieurs classes aux comportements communs.
- Définir une classe abstraite avec des implémentations pour les méthodes communes
- Les classes qui la concrétise redéfiniront le comportement des méthodes qui ont des comportements différents
Permet d'abstraire une machine à état fini sous forme de classes.
- Utilisation d'une interface définissant les différentes transitions
- Implémentation de l'interface par une classe concrète qui utilisera d'autres objets pour effectuer des opérations (un peu comme pour le pattern Proxy)
- Une classe abstraite dont les méthodes renvoient des exceptions
- Cette classe abstraite est implémentée par une classe par État dans lesquels on override que les méthodes nécessaires (comme ca on renvoie automatiquement une execption si une transition n'est pas possible)
- La classe concrète possède un objet courrant (un état) sur lequel seront appelées les méthodes (transitions)
- Structure efficacement le code
- rend les trasitions explicites
- Définir une famille d'algorithmes, les encapsuler et les rendre interchangeables.
- Le pattern permet
- Définition d'une interface communes aux différentes classes d'algorithmes
- Technique de factorisation
- Alternative au sous classes en fonction d'un contexte
- Élimination d'instructions conditionnelles
- Le client doit être mis au courant des différentes stratégies disponibles et de comment elles diffèrent les unes des autres, leurs avantages et inconvénients
- Toutes les stratégies utilisent la même interface, quelle que soit la complexité des différents algos qui l'implémentent. Par exemple, toutes les stratégies implémentées n'utilisent pas tous les paramètres.
Sans violer le principe d'encapsulation, capturer et externaliser l'état interne d'un objet et ainsi permettre de restauré cet état plus tard si besoin
- Déclaration d'une interface sans méthodes implémentée par les objets contenant les états
- Gestion des objets à l'aide d'une autre classe dédiée à cet effet
- L'application dispose d'une collection des différents états des objets et peut ainsi revenir à un état antérieur si besoin
- Encapsuler l'invocation de commandes
- Permettre des fonctionnalité undo/redo
- Création de Macro commandes qui sont composées de plusieurs commandes
- Undo/Redo dans un editeur text
- outils CLI
- Découple l'objet qui invoque la commande de l'objet qui sait comment produire le résultat attendu
- Les commandes deviennent des objets qui peuvent être étendus ou sérialisés
- On peut assembler des commandes en une commande composite
- On peut facilement créer de nouvelle commande en ajoutant une nouvelle classe
- Concept intéressant pour faciliter l'implémentation d'un processus transactionnel
Permet de définir la sémantique opérationnelle d'un langage. Pour cela, on va représenter notre arbre abstrait syntaxique avec un ensemble de classe qui concrétisent une classe abstraite Node. La classe Node possède une méthode "interpret" qui défini la sémantique opérationnelle d'un élément de l'arbre abstrait syntaxique. Cette méthode interpret modifie l'environnement d'exécution (context). Ce pattern fonctionne mieux avec une grammaire simple et si l'éfficacité n'est pas un critère prépondérant. Si c'est le cas, on préfèrera créer un compilateur complet.
- Permet de découpler les opérations sur une structure de données
- Découpler des taches qui ne sont pas liés les unes aux autres
Utiliser une classe abstraite Visitor qu'on pourra implémenter de plusieurs façon en fonction des traitements voulus (ex: X86Visitor, DocHtmlVisitor...)
- Ajout facile de nouvelles fonctionnalités
- Utiliser un accumulateur (ex: table des symboles). Sans le pattern l'accumulateur devrait être passé à toutes les méthodes soit utilisé en variable globale
- Quand on ajoute un type de Node, on doit ajouter des méthodes pour le traiter dans chaques visiteur (sauf si on utilise des classes abstraites pour chaque visiteur avec des comportements par défaut comme ANTRL)
- On casse l'encapsulation (besoin de mettre en public toutes les méhtodes, l'utilisateur pourrait avoir besoin d'un peu tout)
- La traversée de l'AST est déterminée par le programme qu'il représente. On ne choisit pas la facon dont la traversée va se faire.
- Réunir à un seul endroit le code de configuration des objets (ex: relations entre des objets)
- Préparer les objets pour qu'ils puissent coopérer
- Rendre explicite le cycle de vie de ces objets
- Cacher et factoriser la logique de la configuration des objets
- facilité la documentation
- possibilité d'observer la configuration
- Ajouter le pattern observer
- Pattern strategy pour avoir plusieurs configurations dépendant du contexte (ex: online/offline)
- Gérer le cycle de vie des objets avec le pattern State
- Découper en catégories (behavioral, creational, structural)
- Méthodes clone et wakeup pour le pattern singleton
- Générer les diagrammes de classe sur base du code
- Faire une TOC
- Ajouter les patterns vus aux cours (+ tests, description, réponses questions et Diagramme de classe, maj TOC)
- Singleton
- Abstract Factory
- Proxy
- Decorator
- Composite
- Prototype
- Bridge
- Observer
- Intercepteur
- Flyweight
- State
- Visitor
- Template
- Strategy
- Configuration
- Command
- Facade
- Memento
- Playerrole
- Interpreter
Design Patterns PHP (plein de paterns avec exemples de code et diagrammes de classe)
https://designpatternsphp.readthedocs.io/en/latest/README.html
composer test
composer psalm
php generateUmlCD.php
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.