Patterns de conception à connaître en Angular
Pour développer des applications de qualité il est nécessaire d’avoir au préalable une bonne connaissance des patterns de conception existant afin de reconnaitre au plus tôt lorsqu’il est possible est nécessaire d’en faire usage dans le projet.
MVC Pattern
Le MVC design pattern correspond à l’architecture d’un projet composé de trois parties bien distinctes. Les modèles sont utilisés pour représenter les données de transit utilisée dans l’application (interface ou classe sans logique). Les vues correspondent à l’interface utilisateur (le template HTML). Et pour finir les contrôleurs permettent de faire le lien entre les deux couches précédentes.
Dependency Injection Pattern
L'injection de dépendances (DI) est un pattern de conception important pour développer des applications à grande échelle. Angular possède son propre système de DI, qui est utilisé dans la conception d'applications Angular pour augmenter l’efficacité et la modularité. En effet dans le constructeur des classes (composants, directives, services) on demande des dépendances (services ou des objets). C’est un système externe (l’injecteur) qui se charge de créer l’instance en fonction de la configuration. Cela facilite ainsi le développement mais aussi les tests.
SOLID Pattern
Le design pattern SOLID est un ensemble de cinq principes adaptés à la programmation informatique orientée objet. Il a pour but de rendre le design des logiciels plus compréhensible, flexible et maintenable. Ces principes permettent de mettre en évidence les bonnes pratiques à suivre mais ils sont abstraits et il existe donc plusieurs patterns concrets afin de développer en respectant ces principes.
- Responsabilité unique : Chaque fichier (classe / fonction / module / section) ne devrait avoir qu'une seule responsabilité, c'est à dire qu'il doit permettre de faire une unique chose. Il faut donc découper la logique en autant de classe et fichiers que nécessaire. De plus il doit y a voir une place pour tous types de fichiers et chaque fichiers doit être à la bonne place.
- Ouvert-fermé : Chaque classe doit être ouverte à l’extension mais fermée à la modification. Il doit pouvoir être possible d’étendre les fonctionnalités en ajoutant du code uniquement mais sans changer le code existant. C’est à dire que les paramètres d’entrées et sorties des fonctions et les typages ne doivent pas changer. De cette façon il y a moins de risque de casser la logique de fonctionnement.
- Substitution de Liskov : Si B et C sont des implémentations de A, alors B et C doivent pouvoir être interchangées sans affecter l’exécution du programme. Les implémentations B et C doivent avoir les mêmes fonctions, signatures et types afin de pouvoir les interchangées. (Ex : strategy pattern)
- Principe de ségrégation d'interface : Si B et C sont des implémentations de A, alors B et C doivent réellement pouvoir implémenter les fonctions décrites dans l’interface A. Lors de l'exécution de notre programme on ne doit pas vérifier si l'implémentation peut déclencher cette méthode. Si c'est le cas alors la solution est de découper en plusieurs interfaces.
- Principe d'inversion des dépendances : Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau mais les deux doivent dépendre d’abstractions. (Ex : dependency injection pattern, adapter pattern)
Strategy Pattern
Le strategy design pattern consiste à séparer l'exécution d'une logique métier de l'entité qui l'utilise et donnez à cette entité un moyen de basculer entre différentes formes de logique. Par exemple pour pouvoir effectuer différents types de tri sur une entité il faut créer une classe, interface ou fonction abstraite SortingStrategy et créer différentes implémentations du tri. Vous pouvez maintenant sélectionner la stratégie en basculant (idealement via le constructeur) entre les différentes implémentations en fonction de vos besoins. Un autre exemple serait de vouloir utiliser une fonction similaire mais sur plusieurs objets incompatibles (arbe d'héritage différent ou aucun héritage) par exemple pour la fonction afficher il faudra alors créer DisplayStrategy qui sera ensuite utilisé par les objects.
Visitor Pattern
Le visitor pattern est utile si vous avez plusieurs classes concrètes qui héritent de la même classe de base ou implémentent la même interface. Le modèle de visiteur permet d'éviter les blocs if-else ou le switch/case et le forçage de typage. En effet le but est d'ajouter de nouveaux comportements à la hiérarchie de classes existante sans modifier aucun code existant. Par exemple, la classe abstraite CustomMarker déclare une méthode qui doit prendre l'interface du visiteur comme argument, puis chaque implémentation personnalisée de MarkerClass appellera la méthode adéquate du visiteur avec elle-même comme argument. Le visiteur aura donc X méthodes, qui correspond à X implémentation de CustomMarker.
Model-Adapter Pattern
Le design pattern model-adapter permet de transformer le format de données récupérées depuis une source externe dans un format de données adapté pour être consommé par le client Angular. Par exemple si vous récupérez des données depuis un API les dates seront formattés au type String. Il est possible d’effectuer la transformation dès la réception de la réponse de l’API et d’instancier une date au format javascript.
Composition Pattern
L'héritage offert par la programmation orientée objet peut créer des objets hiérarchiques étroitement couplés qui sont difficiles à refactoriser en raison des dépendances d'objet. À la place, vous pouvez utiliser un modèle de composition pour permettre une certaine flexibilité dans ce que l'objet conteneur peut faire.
Lazy Pattern
Le design pattern lazy permet de développer des applications scalables et performantes car le principe de ce pattern est de fournir une application découpée en plusieurs parties indépendantes les unes des autres. Cela permet de réduire la taille de l’application principale et avoir ensuite différents modules qui seront téléchargés par l’utilisateur uniquement s’il souhaite accéder a cette partie précise de l’application.
Singleton Pattern
Ce pattern permet de garantir qu'il n'y a qu'une seule instance de votre classe. Grâce à ce singleton, vous pouvez contrôler la portée des variables à l'intérieur. Le singleton est géré à l'aide d'une méthode publique getInstance qui garantit le seul et unique moyen d'accéder à la classe. Dans Angular, le mécanisme d'injection de dépendances gère le modèle pour nous. Par exemple les services fournis à la racine de l'application sont des instances singleton, au contraire les services fournis dans les composants ne sont pas singleton ils seront instanciés pour chaque instance du composant.
Factory pattern
Ce pattern est très simple mais très utile lorsque vous devez instancier différents objets fils d’une même classe parente en fonction de certaines conditions. La factory définit une interface de création de l'objet avec les conditions de création en entrée et l'instance de l’objet en sortie. Cette interface contient généralement une seule méthode publique. Ensuite, il est possible d’avoir différentes implémentations de cette interface factory avec chacune leurs propres logiques pour instancier les objets. Attention ce pattern factory est nécessaire qu'en cas de logique particulière pour instancier les objets fils, s’il n’y a pas de variance à apporter lors de l’instanciation alors il n’est pas nécessaire d’appliquer ce pattern.
Command pattern
Le but de ce modèle est d'encapsuler une commande à l'intérieur d'un objet. La commande peut être de tout type, par exemple une action synchrone ou même une requête asynchrone. Grâce à cela, vous pouvez avoir une liste de commandes que vous pouvez facilement invoquer, réutiliser, combiner et maintenir dans votre application.
Decorator pattern
Le pattern de conception de décorateur est une alternative aux sous-classes pour étendre un objet en utilisant la composition au lieu de l'héritage. En fait, le décorateur attache des responsabilités supplémentaires à un objet. Le concept principal est d'avoir un objet qui enveloppe un autre objet. Celui qui enveloppe l'objet est le décorateur et c'est le même type de l'objet d'origine mais il a aussi un objet du même type de l'objet.