SOLID
SOLID, c'est un acronyme pour ces 5 principes de programmation.
- S : Single Responsibility Principle
- 0: Open/Closed Principle
- L : Liskov's Substitution Principle
- I : Interface Segregation Principle
- D : Dependency Inversion Principle
SOLID est un ensemble de (seulement) 5 bonnes pratiques dont le but est de rendre le code :
- Moins bogué
- Plus facile à lire
- Plus logique
- Maintenable — Maintenable
- Testable — Testable
- Extensible (tu changes une partie du programme et il continue de fonctionner) — Extensible
Single Responsibility Principale
S : Single Responsibility Principle (SRP) Une classe ne doit avoir qu'une seule et unique responsabilité.
Pourquoi utiliser le principe de responsabilité unique (SRP) ?
- Le code est beaucoup plus clair (une classe de 1000 lignes, ce n'est pas clair).
- Chaque fichier a désormais un rôle qui lui est propre.
- Tout le monde peut comprendre à quoi servent les classes dans le dossier Services/User grâce à au nommage.
- Le projet sera beaucoup plus maintenable, plus facile et agréable à faire évoluer.
S: Single Responsibility Principle (SRP)
Open/Closed Principale
Open/Closed Principle
Les entités doivent être ouvertes à l'extension et fermées à la modification. Cela signifie que l'on doit toujours favoriser l'extension du code à sa modification : on ne modifie pas le fonctionnement suivant l'entité à utiliser, on définit une fonction commune.
Pourquoi utiliser le principe ouvert/fermé (OC) ?
- On aura beaucoup moins de bugs ou de comportements bizarres.
- En termes de maintenabilité, c'est génial, car l'interface et son contrat te permettent de savoir où tu vas.
Open/Closed Principle
Liskov's Substitution Principle
L : Liskov's Substitution Principle
Les objets dans un programme doivent être remplaçables par des instances de leur sous-type sans pour autant altérer le bon fonctionnement du programme.
L'idée du principe est que les enfants ne peuvent pas faire plus ou moins que leur parent.
Voici les 4 conditions que tu dois remplir pour être conforme au Liskov's Substitution Principle.
- La signature des fonctions (paramètres et retour) doit être identique entre l'enfant et le parent.
- Les paramètres de la fonction de l'enfant ne peuvent pas être plus nombreux que ceux du parent.
- Le retour de la fonction doit retourner le même type que le parent.
- Les exceptions retournées doivent être les mêmes.
Pourquoi utiliser le principe de substitution de Liskov (LSP) ?
- Ça évite les bogues où un enfant fait (surtout) plus qu'un parent (plus de paramètres, de retours, d'exceptions levées…).
- Les mises à jour de code seront plus faciles à organiser (chaque changement du parent induit des changements vers les enfants \= sécurité).
- On gagne niveau lisibilité et qualité, le code est beaucoup plus simple à lire et à (ré-)utiliser.
Liskov's Substitution Principle
Interface Segregation Principle
Interface Segregation Principle Aucun client ne devrait être forcé d'implémenter des méthodes / fonctions qu'il n'utilise pas. En résumé… Il vaut mieux faire plusieurs petites interfaces qu'une seule grande.
Pourquoi utiliser le principe de ségrégation des interfaces (ISP) ?
- Améliorer la qualité du code.
- Le code est plus modulable, plus ré-utilisable.
- Éviter les grosses interfaces rendra le code plus facile à lire et à comprendre.
- On respecte aussi le principe de responsabilité unique.
Dependency Inversion Principale
D : Dependency Inversion Principle
Une classe doit dépendre de son abstraction, pas de son implémentation. Autrement dit, on évite de passer des objets en paramètre lorsqu'une interface est disponible.
Passer en paramètre une interface permet d'être certain que l'objet que tu manipules, peu importe son type, aura les bonnes méthodes associées.
Pourquoi utiliser le principe d'inversion de dépendance (DIP) ?
- Un code beaucoup plus facile à modifier, on peut ajouter des fonctionnalités sans crainte de bug.
- Une lisibilité et une qualité de code accrue.
D : Dependency Inversion Principle
Injection de dépendance
La "Dependency Injection" est un "design pattern" qui consiste à séparer l'instanciation (et donc l'implémentation) d'une dépendance et son utilisation. Ce "design pattern" permet :
- d'inverser les dépendances,
- d'éviter le couplage fort avec les dépendances,
- de factoriser l'instanciation d'une dépendance,
- de faciliter le remplacement d'une dépendance par une autre implémentation à des fins fonctionnelles ou de "testing".
IOC Vs DI
IOC (Inversion of Control) est un principe architectural où le contrôle de l'exécution et de la gestion des dépendances est délégué à un framework ou un conteneur, plutôt qu'à l'application elle-même.
DI (Dependency Injection) est une technique spécifique pour réaliser l'IOC, où les dépendances sont injectées dans un objet par un constructeur, un setter, ou une interface, au lieu que l'objet les crée lui-même. En résumé : IOC est le concept global, et DI est l'un de ses moyens d'implémentation.
DRY
DRY simply means (Don't repeat yourself!). Ce principe signifie clairement qu'on devrait essayer d'éviter d'avoir du code dupliqué. Au lieu de cela, on devrait réutiliser notre code lorsque cela est possible.
DRY simply means (Don't repeat yourself!).
KISS
KISS(( Keep It Simple, Stupid )) signifie simplement qu'on doit essayer d'éviter toute complexité inutile. Notre code doit être simple, petit et facile à comprendre.
YAGNI
Le principe YAGNI(You Aren't Gonna Need It) qu'on ne devrait rien ajouter dont on n'a pas strictement besoin. La fonctionnalité ne doit être implémentée dans un programme que lorsqu'il est clair qu'elle est vraiment nécessaire.
YAGNI
Design Pattern
Le Design Pattern ou "23 - patron de conception" se réfère à des solutions pour résoudre des problèmes communs dans la conception de logiciels.Les design patterns sont des pratiques standardisées utilisées par les développeurs pour résoudre des problèmes de conception récurrents et optimisés. Il est classifiés en trois grandes catégories basées sur la nature des problèmes qu'ils cherchent à résoudre : création, structurels, et comportement.
Patterns de Création
Ces patterns concernent le processus de création d'objets. Ils visent à rendre un système indépendant de la manière dont ses objets sont créés, composés, et représentés. Les patterns de création souvent utilisés incluent : *Singleton* : Assure qu'une classe a une seule instance, et fournit un point d'accès global à cette instance. *Factory Method* : Définit une interface pour créer un objet, mais laisse les sous-classes décider quelle classe instancier. *Abstract Factory* : Offre une interface pour créer des familles d'objets liés ou dépendants sans préciser leurs classes concrètes. *Builder* : Sépare la construction d'un objet complexe de sa représentation, permettant le même processus de construction de produire différentes représentations. *Prototype* : Spécifie le genre d'objets à créer en utilisant une instance prototypique, et crée de nouveaux objets en copiant ce prototype.
Patterns de Structurels
Ces patterns s'occupent de la composition des classes et des objets. Ils aident à structurer les systèmes de manière à ce que les classes et objets puissent travailler ensemble plus efficacement.Les patterns structurels incluent :
*Adapter (ou Wrapper)* : Permet à des interfaces incompatibles de travailler ensemble en enveloppant leur propre interface autour d'une interface existante. *Composite* : Compose des objets en structures d'arbre pour représenter des hiérarchies partie-tout. *Proxy* : Fournit un substitut ou placeholder pour un autre objet afin de contrôler l'accès à ce dernier. *Flyweight* : Utilise le partage pour supporter efficacement de grandes quantités d'objets fins. *Façade* : Fournit une interface unifiée à un ensemble d'interfaces dans un sous-système, facilitant ainsi l'utilisation du sous-système. *Bridge* : Découple une abstraction de son implémentation, de sorte que les deux peuvent varier indépendamment.
Patterns de Comportement
Ces patterns sont spécialisés dans la communication entre les objets. Ils aident à définir comment les objets interagissent et se répartissent les responsabilités. Parmi les patterns de comportement, on trouve: *Observer* : Un sujet maintient une liste d'observateurs, tous étant notifiés automatiquement des changements d'état. *Mediator* : Définit un objet qui encapsule la manière dont un ensemble d'objets interagit. Le médiateur favorise le faible couplage en évitant que les objets se réfèrent explicitement les uns aux autres. *Iterator* : Fournit un moyen d'accéder aux éléments d'un objet agrégé séquentiellement sans exposer sa représentation sous-jacente. *Strategy* : Permet à un objet de changer son comportement quand son état interne change. *Command* : Encapsule une requête en tant qu'objet, permettant ainsi de paramétrer les clients avec différentes requêtes, files d'attente, ou demandes de log. State : Permet à un objet de modifier son comportement lorsqu'il change d'état interne.
Pourquoi utilise-t-on les design patterns ? Solutions éprouvées : Les design patterns sont des solutions à des problèmes qui ont été identifiés et résolus de nombreuses fois par d'autres développeurs. Utiliser un design pattern permet d'éviter de réinventer la roue. Facilite la communication : Les design patterns fournissent un lexique commun pour les développeurs, ce qui facilite la communication concernant la structure et le design du code. Améliore la qualité du code : En utilisant des designs patterns, les développeurs peuvent écrire un code plus propre, plus organisé et plus compréhensible, ce qui réduit la probabilité d'erreurs. Facilite la maintenance : Les patterns aident à structurer le code de manière à faciliter sa maintenance et sa mise à jour.
Avantages des design patterns Réutilisabilité : Les patterns peuvent être réutilisés dans différents projets, ce qui réduit le temps de développement et améliore la qualité du logiciel. Extensibilité : Les solutions basées sur des patterns sont généralement plus faciles à étendre, ce qui permet d'ajouter de nouvelles fonctionnalités sans perturber le système existant. Modularité : Les design patterns favorisent la modularité du code, facilitant ainsi le développement parallèle et les tests.
Abstract Factory
Abstract Factory permet de fournir une interface pour créer des familles d'objets liés ou dépendants sans spécifier leurs classes concrètes. Il est utile lorsque le système doit être indépendant de la façon dont ses produits sont créés et assemblés.
Avantages du pattern Il garantit que tous les produits d'une famille fonctionnent ensemble. et facilite l'ajout de nouvelles familles de produits sans modifier le code client. Le code client reste ouvert à l'extension, mais fermé à la modification.
Inconvénients Il ajoute des couches d'abstraction.Si un nouveau produit doit être ajouté, toutes les fabriques doivent être mises à jour.
Builder
Builder est utilisé pour construire des objets complexes étape par étape. Il sépare le processus de construction de la représentation finale, ce qui permet de créer différents types d'objets en utilisant le même processus. Le Builder est utile dans les cas
- Lorsqu' on veut rendre la construction d'un objet plus lisible ou réduire le risque d'erreur lié à un constructeur avec trop d'arguments
- Lorsqu' on souhaite personnaliser ou configurer l'objet sans exposer directement ses détails d'implémentation
Avantages du Builder
- Le Builder rend la construction d'objets complexes intuitive et flexible grâce à l'approche "étape par étape" (Lisibilité et flexibilité ).
- Il permet de créer des objets immuables, car le processus de construction est contrôlé avant de renvoyer l'objet (Immutabilité).
- Ajout facile de nouvelles étapes de configuration sans casser l'interface existante (Code évolutif )
Inconvénients
- Il peut être inutile pour des objets simples (Surcharge de complexité ).
- Si plusieurs types de produits doivent être construits, chaque produit peut nécessiter son propre Builder (Multiples Builders).
Quand ne pas utiliser Builder ?
- Si l'objet à construire est simple, il est préférable d'utiliser un constructeur avec des paramètres ou un pattern Factory.
- Si l'objet n'a pas beaucoup de variations ou de configurations possibles.
Structure du Builder Les acteurs principaux du pattern Builder :
- Builder : Définit une interface pour la construction d'un produit.
- ConcreteBuilder : Implémente l'interface et construit une version spécifique du produit.
- Product : L'objet complexe à construire.
- Director (optionnel) : Une classe facultative qui orchestre le processus de construction étape par étape.
Factory Method
Le Factory Method est un patron de création qui définit une interface pour créer un objet, mais laisse le choix des classes concrètes aux sous-classes. Cela permet de découpler le code client de la logique de création des objets.
Avantages
- Découplage : Le client n'a pas besoin de connaître les classes concrètes des produits.
- Extensibilité : Facile d'ajouter de nouveaux types de produits en créant de nouvelles sous-classes de Creator.
- Réutilisabilité : La logique commune est centralisée dans la classe Creator.
Inconvénients
- Complexité : Nécessite plusieurs classes supplémentaires, même pour des cas simples.
- Sous-classes nécessaires : on doit créer une nouvelle sous-classe pour chaque type de produit, ce qui peut augmenter le nombre de classes.
Quand ne pas utiliser ?
- Si la logique de création est simple ou si vous n'avez qu'un ou deux types d'objets.
- Si vous pouvez utiliser un Simple Factory (une classe avec une méthode statique pour créer des objets).
Singleton
Singleton permet de s'assurer qu'une classe ne possède qu'une seule instance dans tout le programme peu importe combien de fois on tente d'en créer et fournit un point d'accès global à cette instance c'est à dire qu'il Il offre un moyen standard et contrôlé d'accéder à cette unique instance, généralement via une méthode statique (par exemple, getInstance()).
Comment fonctionne le Singleton ?
Un Singleton typique suit ces étapes :
- Le constructeur de la classe est défini comme privé pour empêcher l'instanciation directe via new.
- Une instance statique privée de la classe est déclarée à l'intérieur de la classe elle-même.
- Une méthode publique (souvent nommée getInstance()) est utilisée pour vérifier si l'instance existe déjà. Si ce n'est pas le cas, elle est créée, sinon on retourne celle qui existe.
Pourquoi utiliser le Singleton ?
Le Singleton est utile dans des situations où il est nécessaire d'assurer qu'une seule instance d'une classe existe, pour éviter des conflits ou pour économiser des ressources.
- Gestion des ressources partagées : Par exemple, un pool de connexions à une base de données ou un gestionnaire de fichiers.
- Configuration globale : Si un programme a besoin d'une configuration accessible partout, on peut stocker cette configuration dans un Singleton.
- Journalisation (logging) : Pour centraliser les logs dans un système.
- Gestionnaire d'état unique : Pour maintenir un état global (comme un état d'application dans un jeu vidéo).
Avantages
- Contrôle centralisé : Le Singleton centralise l'accès à une instance unique.
- Économie de ressources : Évite la création d'instances multiples inutiles.
- Facilité d'accès : Fournit un point d'accès unique et global.
Inconvénients
- Risque de dépendance globale : Comme il est accessible partout, cela peut rendre le code difficile à tester ou à maintenir.
- Problèmes de multithreading : Si plusieurs threads accèdent simultanément à la méthode de création, cela peut entraîner des problèmes de concurrence, sauf si on gère explicitement la synchronisation.
- Couplage fort : Les classes peuvent devenir fortement dépendantes du Singleton, ce qui rend l'évolution ou la modularité du code plus difficile.
Alternatives modernes
Dans certains cas, le Singleton peut être remplacé par d'autres approches, comme l'injection de dépendances ou les conteneurs IoC (Inversion of Control), qui permettent une gestion plus flexible et moins couplée.
Prototype
Prototype permet de créer de nouveaux objets en copiant ou clonant un objet existant plutôt qu'en instanciant une nouvelle classe directement. Ce pattern repose sur l'idée de fournir un modèle ou un prototype" à partir duquel on peut produire des copies.
Pourquoi utiliser le Prototype ?
- La création d'un nouvel objet via clonage peut être plus rapide que la création via un constructeur complexe .
- Cela permet d'éviter une logique complexe dans le constructeur de la classe.
- Utile lorsqu'il y a une hiérarchie de classes et que l'on souhaite éviter des instanciations complexes.
- Le prototype permet de créer des objets avec des configurations initiales différentes sans avoir besoin de redéfinir des constructeurs ou des classes.
Quand utiliser le Prototype ?
- Créer un objet est coûteux ou complexe :Par exemple, un objet qui nécessite une configuration lourde ou des données externes.
- Système à objets dynamiques :Lorsque des classes ou leurs états sont déterminés au moment de l'exécution.
- Besoin de conserver des états similaires :Si vous avez besoin de plusieurs objets avec des valeurs initiales identiques ou proches.
- Éviter les dépendances :Réduire le couplage à des classes concrètes et privilégier la duplication des objets déjà configurés.
Avantages :
- Réduction des coûts de création : Surtout si l'objet initial est complexe à créer.
- Découplage : Permet de travailler avec des interfaces d'objets sans connaître leur implémentation précise.
- Facilité d'évolution : Si le prototype change, les clones héritent de ces modifications (si nécessaire).
Inconvénients :
- Gestion des dépendances circulaires : Un clonage profond peut être nécessaire, ce qui n'est pas trivial.
- Complexité accrue : Introduit une couche supplémentaire de complexité pour gérer les clones.
- Risque de copies non souhaitées : Les modifications sur les clones doivent être bien maîtrisées.
Adapter ou wrapper
Adapter permet de faire la transition entre deux interfaces incompatibles. Il sert de "pont" pour permettre à deux objets de communiquer, même s'ils n'ont pas d'interface commune, en transformant l'interface d'un objet en celle qu'attend un autre objet. L'idée principale du pattern Adapter est de transformer l'interface d'une classe en une interface attendue par une autre classe. Cela permet à des classes avec des interfaces incompatibles de travailler ensemble sans avoir à modifier leur code source.
Composants du Pattern Adapter :
- Client : L'entité qui attend une certaine interface.
- Target : L'interface attendue par le client.
- Adapter : L'entité qui fait la conversion entre les interfaces. Elle adapte l'interface de l'objet "Adaptee" vers celle attendue par le client.
- Adaptee : L'interface qui n'est pas compatible avec celle attendue par le client, mais qui est néanmoins utile et peut être adaptée.
Pourquoi utiliser le pattern Adapter ?
- Interopérabilité :Si on a un code existant qui fonctionne avec une certaine interface, mais que l'on souhaite utiliser une nouvelle classe qui a une interface différente, l'adapter permet d'intégrer ces deux composants sans modification majeure du code.
- Éviter les modifications coûteuses :Si on ne peut pas ou ne veut pas modifier le code de l'objet ou de la classe que vous souhaitez utiliser, l'Adapter permet d'adapter cette classe à vos besoins sans avoir à toucher à son implémentation interne.
- Simplification de l'intégration :L'Adapter peut simplifier l'intégration de systèmes ou de bibliothèques externes en créant des "interfaces" compatibles avec celles de votre application.
- Facilite le changement d'implémentation :On peut changer l'implémentation sous-jacente (par exemple, passer d'une bibliothèque à une autre) sans affecter le code client, à condition que vous adaptiez l'interface correctement.
Quand utiliser l'Adaptateur ?
- Lorsqu'on travaille avec des bibliothèques ou des frameworks tiers dont l'interface ne correspond pas à celle de notre système.
- Lorsqu' on souhaite préserver la compatibilité avec du code existant tout en intégrant de nouvelles fonctionnalités.
- Lorsqu'on doit intégrer des classes qui ont des interfaces différentes mais qui accomplissent des tâches similaires ou liées.
Composite
Composite est un pattern de structure utilisé pour représenter une hiérarchie d'objets de manière à ce qu'un client puisse interagir avec des objets individuels ou des groupes d'objets de manière uniforme. Il permet de traiter de manière identique un objet simple (feuille) et une collection d'objets (composite).
Pourquoi utiliser le pattern Composite ?
- Uniformité :Permet de traiter les objets individuels et les groupes d'objets de manière uniforme.Cela simplifie grandement le code client, car celui-ci n'a pas besoin de connaître la différence entre un objet simple et un groupe.
- Hiérarchies complexes : Idéal pour représenter des structures hiérarchiques (par exemple, un système de fichiers, une organisation avec des employés et des départements, ou une scène graphique).
- Réduction de la complexité :En cachant les détails de la composition, il simplifie la manipulation des objets dans des structures complexes.
- Flexibilité : Ajout ou suppression d'objets dans la hiérarchie sans modifier le code client.
Quand utiliser le pattern Composite ?
- Systèmes hiérarchiques : Lorsqu'on doit modéliser des objets qui sont organisés en une hiérarchie ou un arbre.
- Traitement uniforme : Lorsqu'on veut que le client interagisse avec des objets simples ou des collections d'objets de la même manière.
- Manipulation récursive : Lorsque les opérations sur les objets doivent s'appliquer de manière récursive sur l'ensemble de la structure (comme parcourir un arbre ou effectuer une somme totale).
Avantages du Pattern Composite
- Traitement uniforme :Les feuilles et composites sont traités de manière uniforme, ce qui simplifie le code client.
- Facilité d'ajout et de suppression :Les nouveaux types de composants ou modifications dans la hiérarchie sont faciles à intégrer.
- Structure flexible :Permet de modéliser facilement des structures d'arbre complexes.
- Simplicité pour les opérations récursives :Les algorithmes récursifs comme les traversées d'arbre sont faciles à implémenter avec ce pattern.
Inconvénients du Pattern Composite
- Complexité accrue : Introduit des abstractions supplémentaires, ce qui peut compliquer le code pour des structures simples.
- Problèmes de gestion :Si la hiérarchie est mal définie, il peut devenir difficile de comprendre et de manipuler la structure.
- Potentiel de surcoût :Dans certains cas, il peut y avoir un surcoût en termes de performances ou de mémoire, en particulier si la hiérarchie est trop profonde ou complexe.
Proxy
Proxy est un pattern de structure qui agit comme un intermédiaire entre un client et un objet réel. Le Proxy contrôle l'accès à cet objet réel, permettant d'ajouter des fonctionnalités ou des contraintes (comme le contrôle d'accès, la gestion des ressources ou le cache) sans modifier l'objet lui-même. Il est une classe qui implémente la même interface que l'objet qu'elle représente, mais elle intercepte les appels au véritable objet pour y ajouter un comportement supplémentaire. Le Proxy peut ainsi contrôler l'accès à l'objet réel en fonction de différents besoins.
Pourquoi utiliser le Pattern Proxy ? Le pattern Proxy est utilisé pour résoudre plusieurs problèmes dans les systèmes où le contrôle, la gestion ou la protection de l'accès aux ressources sont nécessaires. Voici les principales raisons d'utiliser un Proxy :
- Contrôle d'accès :Restreindre ou gérer l'accès à un objet, par exemple, dans un système sécurisé où seuls certains utilisateurs peuvent accéder à certaines ressources.
- Optimisation des ressources : Charger des objets lourds en termes de ressources uniquement lorsque cela est nécessaire (Proxy paresseux ou Lazy Proxy).
- Amélioration des performances :Réduire les appels coûteux (par exemple, vers une base de données ou un service distant) grâce à un Proxy de mise en cache (Cache Proxy).
- Abstraction des opérations distantes :Déléguer l'appel à un objet situé sur un serveur distant sans que le client en soit conscient (Remote Proxy).
Structure du Pattern Proxy Le Proxy repose sur trois composants principaux :
- Subject (Sujet) :Interface commune pour le Proxy et l'objet réel. Cela garantit que le client peut interagir de manière uniforme avec le Proxy ou l'objet réel.
- RealSubject (Objet Réel) :L'objet que le Proxy représente. C'est là que la logique principale est réellement exécutée.
- Proxy :Intermédiaire qui implémente l'interface du Subject et ajoute un comportement supplémentaire avant ou après de déléguer les appels à l'objet réel.
Avantages du Pattern Proxy
- Contrôle d'accès :Permet de limiter ou sécuriser l'accès à des objets sensibles.
- Optimisation des performances :Permet de différer ou de réduire la consommation des ressources (Lazy loading, mise en cache).
- Modularité :Ajoute des fonctionnalités supplémentaires sans modifier l'objet réel.
- Abstraction des opérations complexes :Cache les détails d'implémentation comme les communications réseau ou la gestion des ressources lourdes.
Inconvénients du Pattern Proxy
- Complexité accrue :Ajoute une couche supplémentaire d'abstraction, ce qui peut compliquer le code.
- Difficulté de débogage : Il peut être difficile de suivre les appels lorsqu'ils passent par un Proxy.
- Surcharge potentielle : Si le Proxy ajoute trop de logique ou de traitement, cela peut entraîner une baisse de performances.
Flyweight
Flyweight est un pattern structurel utilisé pour minimiser l'utilisation de mémoire lorsqu'un grand nombre d'objets similaires ou identiques doivent être créés. Ce pattern réduit la duplication de données en partageant les états communs entre plusieurs objets.
L'idée du Flyweight est de réutiliser autant que possible les objets existants, au lieu d'en créer de nouveaux, en partageant leurs parties communes. Pour cela, il divise l'état d'un objet en deux parties :
- État intrinsèque : Les données qui sont constantes et communes à plusieurs objets (elles peuvent être partagées).
- État extrinsèque : Les données qui varient d'un objet à l'autre et qui ne peuvent pas être partagées.Cet état est souvent fourni par le contexte où l'objet est utilisé.
Pourquoi utiliser le Pattern Flyweight ? Le Flyweight est utilisé pour résoudre les problèmes liés à l'utilisation excessive de mémoire et aux performances lorsque :
- On avait une grande quantité d'objets similaires.
- Ces objets consomment beaucoup de ressources (par exemple, mémoire ou CPU).
- On veut optimiser l'utilisation des ressources en évitant de stocker plusieurs fois les mêmes données.
Problèmes résolus par le Flyweight
- Réduction de la consommation mémoire : Si vous avez des millions d'objets qui partagent les mêmes données, le Flyweight évite la duplication de ces données en les partageant entre tous les objets similaires.
- Amélioration des performances :Moins d'objets signifie une réduction des coûts de gestion (instanciation, destruction, collecte de déchets, etc.).
- Gestion efficace des objets : Simplifie la gestion des ressources dans des systèmes nécessitant une grande échelle.
Quand utiliser le Flyweight ?
- Nombre élevé d'objets similaires : Lorsque le système doit gérer un grand nombre d'objets presque identiques.
- Données partagées : Lorsque les objets ont un état qui peut être divisé en une partie partagée et une partie spécifique.
- Limitation des ressources : Lorsque la mémoire est un facteur critique, comme dans les systèmes embarqués ou les applications graphiques.
Avantages
- Réduction de la consommation de mémoire : Le partage des données permet d'économiser beaucoup de mémoire, surtout dans les systèmes où les objets sont redondants.
- Performances améliorées : Réduction du temps de création et de destruction d'objets, ce qui améliore les performances.
- Meilleure modularité : L'état intrinsèque est isolé et partagé, tandis que l'état extrinsèque est géré par le client.
Inconvénients
- Complexité accrue : Le système devient plus complexe à cause de la gestion explicite de l'état intrinsèque et extrinsèque.
- Accès synchronisé : Si les Flyweights sont utilisés dans un environnement multithreadé, il peut être nécessaire de synchroniser l'accès pour éviter des problèmes de concurrence.
- Coût d'administration : Le suivi et la gestion des objets partagés dans le cache de la fabrique peuvent introduire des surcharges supplémentaires.
Façade
Façade est un pattern structurel qui fournit une interface simplifiée et unifiée à un ensemble de classes ou de sous-systèmes complexes. Il agit comme un point d'entrée unique pour le client, masquant la complexité des sous-systèmes sous-jacents et facilitant leur utilisation.
L'idée principale est de créer une classe Façade qui regroupe et masque les détails des opérations complexes d'un sous-système composé de plusieurs classes. Le client interagit uniquement avec la façade, sans avoir à comprendre la structure interne du sous-système.
Pourquoi utiliser le Pattern Façade ?
- Simplification de l'interface utilisateur :Fournit une interface unique et simple pour interagir avec des sous-systèmes complexes.
- Réduction du couplage :Le client ne dépend pas directement des classes ou des composants internes du sous-système.
- Encapsulation des détails :Cache les détails de l'implémentation du sous-système, rendant le code plus modulaire et plus facile à maintenir.
- Organisation du code :Facilite l'organisation d'un système complexe en séparant les couches de logique métier.
Problèmes résolus par le Pattern Façade
- Complexité excessive : Lorsqu'un sous-système a de nombreuses classes et méthodes, la façade fournit un moyen simple et cohérent d'interagir avec lui.
- Couplage fort entre le client et le sous-système :En masquant les détails de l'implémentation, la façade réduit la dépendance du client vis-à-vis des classes internes.
- Difficulté de maintenance :En isolant le client des sous-systèmes internes, les changements au niveau des sous-systèmes n'impactent pas directement le client.
- Code désorganisé :En regroupant les interactions avec le sous-système dans une seule classe, la façade améliore la lisibilité et la maintenabilité.
Quand utiliser le Pattern Façade ?
- Lorsqu'on travaille avec un sous-système complexe qui nécessite de nombreuses classes pour fonctionner.
- Lorsqu'on souhaite fournir une interface plus simple pour faciliter l'utilisation de ce sous-système.
- Lorsqu'on veut réduire les dépendances entre le client et les sous-systèmes internes.
- Lorsque on développe des couches logicielles où la façade peut servir de point d'entrée à une couche donnée.
Avantages du Pattern Façade
- Simplification de l'utilisation : Fournit une interface unique, réduisant la complexité pour le client.
- Réduction du couplage : Le client est découplé des sous-systèmes internes, ce qui facilite les changements et la maintenance.
- Isolation des modifications : Les changements dans le sous-système n'affectent pas directement le client tant que l'interface de la façade reste la même.
- Amélioration de la lisibilité et de l'organisation :Centralise les interactions avec les sous-systèmes.
- Interopérabilité entre systèmes : Permet d'intégrer facilement de nouveaux composants ou systèmes tiers.
Inconvénients du Pattern Façade
- Abstraction excessive : Si la façade masque trop de détails, elle peut rendre le système moins flexible pour les utilisateurs avancés.
- Risque de dépendance à la Façade : Le client peut devenir trop dépendant de la façade, ce qui limite l'accès direct aux fonctionnalités spécifiques des sous-systèmes.
- Complexité cachée : La façade peut cacher une logique complexe, rendant le débogage plus difficile en cas de problème dans le sous-système.
Bridge
Bridge est un pattern structurel utilisé pour découpler une abstraction de son implémentation, de manière à ce que les deux puissent évoluer indépendamment. Il est particulièrement utile pour gérer des situations où des objets doivent être combinés avec différentes implémentations sans créer une explosion de classes.
Au lieu de lier directement l'abstraction à une implémentation particulière, le Bridge insère une interface (ou classe abstraite) intermédiaire pour que l'abstraction puisse interagir dynamiquement avec l'implémentation.
Problèmes résolus par le Pattern Bridge
- Explosion de combinaisons de classes : Si vous avez plusieurs variantes d'une abstraction et plusieurs implémentations possibles, une approche classique nécessiterait de créer une classe pour chaque combinaison. Le Bridge évite cela.
- Couplage rigide : Sans le Bridge, l'abstraction et l'implémentation sont fortement couplées, ce qui rend difficile leur modification ou leur extension indépendamment.
- Problèmes de maintenabilité : Une hiérarchie de classes complexe est difficile à maintenir. En séparant les responsabilités, le Bridge simplifie cette tâche.
Quand utiliser le Pattern Bridge ?
- Lorsqu'on a besoin de séparer l'abstraction de son implémentation.
- Lorsqu'on veut éviter de créer une multitude de classes pour gérer différentes combinaisons d'abstractions et d'implémentations.
- Lorsque les abstractions et les implémentations doivent pouvoir évoluer indépendamment.
- Lorsque vous travaillez avec un système multi-plateforme ou multi-technologies.
Avantages du Pattern Bridge
- Réduction de la complexité : Permet d'éviter une explosion de classes en séparant les dimensions d'abstraction et d'implémentation.
- Découplage : Les abstractions sont indépendantes des implémentations, ce qui facilite les modifications dans une partie sans affecter l'autre.
- Extensibilité : Ajout facile de nouvelles abstractions ou implémentations sans affecter le reste du code.
- Reutilisabilité : Les implémentations peuvent être réutilisées dans différentes abstractions.
Inconvénients du Pattern Bridge
- Complexité initiale : Le Bridge introduit des abstractions supplémentaires qui peuvent compliquer la conception initiale du système.
- Utilisation inutile : Si le système est simple ou ne nécessite pas de séparation entre abstraction et implémentation, le Bridge peut ajouter une surcharge inutile.
Observer
Observer est un pattern comportemental qui définit une relation de type "un-à-plusieurs" entre des objets. Lorsqu'un objet (appelé sujet) change d'état, tous les objets qui s'y sont abonnés (appelés observers ou observateurs) sont automatiquement notifiés et mis à jour.
Structure du Pattern Observer
Voici les principaux éléments du pattern :
- Subject (Sujet) : Contient l'état à observer. Il gère une liste d'observateurs et notifie les observateurs lorsqu'un changement d'état survient.
- Observer (Observateur) : Une interface ou classe abstraite que les observateurs implémentent. Déclare une méthode (souvent update()) appelée lorsque le sujet change d'état.
- Concrete Observer (Observateur Concret) : Implémente l'interface Observer et Met à jour son propre état en fonction des notifications du sujet.
- Concrete Subject (Sujet Concret) : Étend la classe ou interface Subject et gère l'état et notifie les observateurs en cas de modification.
- Client :Configure les relations entre le sujet et les observateurs.
Quand utiliser le Pattern Observer ?
- Notification d'état : Lorsqu'un changement d'état dans un objet doit entraîner une mise à jour automatique de plusieurs autres objets.
- Relation un-à-plusieurs : Lorsque plusieurs objets dépendent d'un seul objet principal.
- Faible couplage : Lorsque vous voulez éviter que le sujet ait une connaissance directe de ses observateurs.
- Systèmes événementiels : Pour gérer des événements dans des interfaces utilisateur ou des systèmes réactifs.
Avantages du Pattern Observer
- Faible couplage : Le sujet ne dépend pas directement de la structure ou du comportement des observateurs.
- Réutilisabilité : Les observateurs peuvent être réutilisés avec différents sujets.
- Notification automatique : Les observateurs sont automatiquement mis à jour en cas de changement d'état dans le sujet.
- Extensibilité : De nouveaux observateurs peuvent être ajoutés facilement sans modifier le sujet.
Inconvénients du Pattern Observer
- Notification non contrôlée : Si le sujet notifie trop souvent les observateurs ou dans des cas inutiles, cela peut entraîner des performances médiocres.
- Dépendance implicite : La relation entre le sujet et les observateurs peut devenir difficile à suivre, notamment dans un système complexe.
- Problèmes de performance : Si le nombre d'observateurs est élevé ou si les notifications sont fréquentes, cela peut engendrer des ralentissements.
- Ordre des notifications : Les observateurs sont souvent notifiés dans un ordre arbitraire, ce qui peut poser problème si l'ordre est important.
Mediator
Mediator est un pattern comportemental qui centralise la communication entre plusieurs objets ou composants dans un système. Au lieu que ces objets communiquent directement entre eux, ils passent par un objet intermédiaire appelé médiateur. Cela permet de réduire le couplage entre les composants, rendant le système plus flexible et facile à maintenir.
Quand utiliser le Pattern Mediator ?
- Lorsque plusieurs objets ou composants doivent interagir de manière complexe.
- Lorsque les dépendances entre objets deviennent trop nombreuses et difficiles à gérer.
- Lorsqu'on veut centraliser et encapsuler les règles de communication dans un seul endroit.
- Dans les systèmes où vous avez besoin d'ajouter ou de modifier des composants sans casser le fonctionnement des autres.
Structure du Pattern Mediator
Les principaux éléments du Mediator :
- Mediator (Interface du Médiateur) : Définit une interface pour communiquer avec les objets participants.
- Concrete Mediator (Médiateur Concret) : Implémente l'interface Mediator et orchestre les interactions entre les objets participants.
- Colleagues (Collègues) : Représentent les objets ou composants qui participent à la communication. Ils communiquent uniquement avec le médiateur, et non entre eux.
- Client : Configure les objets participants et le médiateur.
Avantages du Pattern Mediator
- Réduction du couplage : Les objets collègues ne communiquent pas directement entre eux, ce qui réduit les interdépendances.
- Centralisation de la logique : La logique de communication est concentrée dans le médiateur, facilitant sa gestion et sa maintenance.
- Facilité d'ajout ou de modification : Les collègues peuvent être ajoutés ou modifiés sans affecter les autres composants du système.
- Meilleure lisibilité : En remplaçant les communications complexes directes par une centralisation, le code devient plus clair et plus compréhensible.
Inconvénients du Pattern Mediator
- Complexité accrue dans le médiateur : Si trop de logique est centralisée dans le médiateur, celui-ci peut devenir difficile à maintenir et enfreindre le principe de responsabilité unique.
- Dépendance au médiateur : Tous les collègues dépendent du médiateur, ce qui peut rendre le système vulnérable en cas de problème avec ce dernier.
- Sur-ingénierie possible : Dans un système simple, l'introduction d'un médiateur peut ajouter une complexité inutile.
Iterator
Iterator est un patron de conception comportemental qui permet de parcourir les éléments d'une collection (comme une liste, un tableau ou une structure complexe) sans exposer les détails internes de cette collection.
Iterator découple le parcours d'une collection de l'implémentation de la collection elle-même. Cela permet :
- Accès uniforme : on peut parcourir différents types de collections de manière uniforme, sans connaître leur structure interne.
- Encapsulation : L'implémentation interne de la collection reste cachée. L'utilisateur interagit uniquement avec l'itérateur.
- Flexibilité : On peut créer des itérateurs personnalisés qui suivent des règles de parcours spécifiques (par exemple, parcourir dans un ordre particulier).
Problème résolu par le design pattern Iterator Le problème que résout ce design pattern est comment parcourir une collection d'éléments sans exposer les détails de son implémentation interne.
Avant le pattern Iterator :
- L'accès aux éléments d'une collection nécessitait souvent de connaître sa structure interne (par exemple, si c'était un tableau, une liste ou une autre structure de données).
- Si l'implémentation de la collection changeait, tout le code dépendant devait être modifié.
Avec le pattern Iterator :
- On interagit avec un itérateur standard, indépendant de la structure interne de la collection.
- Cela permet de respecter le principe d'encapsulation et de réduire les dépendances entre les classes.
Avantages du pattern Iterator
- Simplifie le code client : Le client n'a pas besoin de connaître les détails de la collection.
- Encapsulation respectée : Le client interagit uniquement avec l'itérateur, pas avec la collection elle-même.
- Polymorphisme : Le même code de parcours peut être utilisé pour différents types de collections.
- Extensibilité : On peut ajouter de nouveaux types de parcours sans modifier les collections existantes.
Strategy
Strategy est un patron de conception comportemental qui permet de définir une famille d'algorithmes, de les encapsuler dans des classes distinctes et de les rendre interchangeables à la volée. Ce pattern sépare clairement le comportement d'une classe de son utilisation, ce qui favorise une meilleure modularité et flexibilité.
Problème résolu par le design pattern Strategy Le pattern Strategy résout le problème suivant : Comment permettre à une classe de choisir dynamiquement un comportement parmi plusieurs options sans altérer son code ni dupliquer des implémentations ?
Exemple typique du problème : Supposons qu'on ait une classe qui effectue une opération (par exemple, un calcul de prix ou un tri). Si on implémente toutes les variantes dans cette même classe, on obtient du code :
- Difficile à lire et à maintenir (gros blocs de conditions if-else ou switch-case).
- Rigide : ajouter de nouvelles stratégies implique de modifier le code existant.
- Non conforme au principe de responsabilité unique.
Le pattern Strategy permet de déléguer ces comportements à des classes distinctes.
Structure du pattern Strategy
- Context : La classe principale qui utilise une stratégie. Elle possède une référence à une stratégie (interface ou classe abstraite).
- Strategy (Interface ou classe abstraite) : Définit une méthode commune pour tous les algorithmes.
- Concrete Strategy :Les différentes implémentations des algorithmes ou comportements.
Avantages du design pattern Strategy
- Respect du principe de responsabilité unique : Le comportement est séparé dans des classes spécifiques (stratégies). La classe principale (le contexte) ne gère pas les détails des comportements.
- Facilité de modification et d'extension : Ajouter une nouvelle stratégie n'implique pas de modifier les classes existantes. Il suffit de créer une nouvelle implémentation de la stratégie.
- Réutilisation du code : Les stratégies peuvent être réutilisées dans différents contextes.
- Flexibilité : Le comportement peut être changé dynamiquement en remplaçant la stratégie utilisée.
- Élimination des conditions complexes : Évite les blocs if-else ou switch-case pour choisir un comportement.
Inconvénients du design pattern Strategy
- Augmentation du nombre de classes : Chaque stratégie nécessite une classe séparée, ce qui peut alourdir le projet, notamment si le nombre de stratégies est important.
- Complexité initiale : Pour des cas simples, utiliser le pattern Strategy peut être excessif, car il introduit une abstraction supplémentaire.
- Déplacement de la logique : Bien que le comportement soit découplé, il peut devenir difficile de comprendre l'ensemble du flux lorsqu'il y a de nombreuses stratégies.
- Couplage au niveau du contexte : Le contexte doit avoir une référence à la stratégie, ce qui peut compliquer les tests unitaires ou la gestion des dépendances.
Command
Command est un patron de conception comportemental qui transforme une requête ou une action en un objet indépendant. Cela permet de découpler l'émetteur d'une requête (celui qui demande l'exécution) du receveur (celui qui exécute la requête).
Chaque commande encapsule tous les détails nécessaires pour exécuter une action, comme le destinataire de la requête, les paramètres nécessaires, et la méthode à appeler.
Problème résolu Le problème que résout ce pattern est comment permettre l'encapsulation et la gestion d'actions ou de requêtes dans un système, tout en rendant le code flexible et extensible. Cela est particulièrement utile dans des cas où :
- Les actions doivent être reportées ou annulées : Par exemple, dans un système avec un bouton "Annuler" ou une pile d'actions exécutées.
- On souhaite que l'émetteur et le receveur de l'action soient découplés : L'émetteur (par exemple, une interface utilisateur) n'a pas besoin de savoir qui ou comment l'action sera effectuée.
- Les actions doivent être stockées et rejouées : On peut enregistrer des commandes pour les exécuter plus tard, les annuler ou les rejouer.
Structure du pattern Command
- Command (Interface ou classe abstraite) : Déclare une méthode execute() pour exécuter une commande.
- Concrete Command : Implémente l'interface Command et encapsule une action spécifique et son receveur.
- Receiver : L'objet qui effectue réellement l'action. Il contient la logique métier.
- Invoker : L'objet qui déclenche la commande (par exemple, une interface utilisateur ou un bouton).
- Client : Configure les commandes et les lie aux invokers.
Avantages
- Découplage entre l'émetteur et le receveur : L'émetteur (par exemple, l'interface utilisateur) n'a pas besoin de savoir qui exécute la commande ni comment elle est exécutée.
- Extensibilité : Ajouter une nouvelle commande est simple : il suffit de créer une nouvelle classe qui implémente l'interface Command.
- Historique des actions : Les commandes peuvent être stockées dans une pile pour permettre des fonctionnalités comme "annuler" ou "répéter".
- Réutilisation des commandes : Les commandes encapsulent les actions, ce qui permet de les utiliser dans différents contextes.
- Programmation différée : Les commandes peuvent être planifiées pour être exécutées plus tard.
- Uniformité : Un même invoker peut travailler avec différentes commandes sans avoir à les traiter différemment.
Inconvénients
- Augmentation du nombre de classes : Chaque commande nécessite une classe séparée, ce qui peut alourdir le projet, surtout si le nombre de commandes est élevé.
- Complexité initiale : Pour des cas simples, le pattern peut sembler inutilement complexe.
- Gestion des dépendances : Le client doit souvent lier les commandes avec leurs invokers et receveurs, ce qui peut compliquer la configuration.
Chain of Responsibility
Chain of Responsibility est un patron de conception comportemental qui permet à un objet de traiter une demande ou une requête de manière progressive, en la passant le long d'une chaîne d'objets. Chaque objet dans la chaîne a la possibilité de traiter la requête ou de la transmettre à l'objet suivant dans la chaîne.
Ce modèle permet de séparer l'émetteur de la requête du récepteur, tout en déléguant la responsabilité à plusieurs objets possibles.
Problème résolu Le problème que résout ce pattern est comment éviter un couplage trop fort entre l'émetteur d'une requête et le récepteur, et permettre à une série d'objets de traiter la requête sans qu'elle soit toujours dirigée vers un seul récepteur spécifique.
Exemple typique du problème :
- On a plusieurs objets qui peuvent traiter une demande, mais on ne sait pas à l'avance lequel d'entre eux pourra le faire. L'usage de conditions if-else ou switch rendrait le code moins flexible et difficile à maintenir.
- Avec une chaîne de responsabilité, on crée une chaîne d'objets où chacun décide de traiter ou non la demande et de la transmettre à l'objet suivant si nécessaire.
Le pattern permet donc un traitement plus flexible et modulaire des requêtes.
Structure du pattern
- Handler (interface ou classe abstraite) : Déclare une méthode handleRequest() pour traiter la requête et, si nécessaire, passer la requête au handler suivant dans la chaîne.
- ConcreteHandler : Chaque classe concrète implémente la méthode handleRequest() et décide si elle doit traiter la requête ou la transmettre au prochain handler.
- Client : Envoie la requête à l'objet de départ dans la chaîne (le premier handler).
- Chain : La chaîne d'objets qui traitent la requête.
Avantages
- Réduction du couplage : L'émetteur de la requête n'a pas besoin de connaître le récepteur exact. Il envoie simplement la demande au début de la chaîne.
- Flexibilité dans le traitement des requêtes : on peut ajouter ou supprimer des gestionnaires dans la chaîne sans modifier l'émetteur ou les autres gestionnaires.
- Responsabilité partagée : Les objets de la chaîne partagent la responsabilité du traitement des requêtes, ce qui permet de mieux répartir la charge de travail.
- Facilité d'extension : Il est facile d'ajouter de nouveaux types de gestionnaires à la chaîne sans affecter le code existant.
- Réutilisation : Les gestionnaires peuvent être réutilisés dans différents contextes en fonction des chaînes dans lesquelles ils sont placés.
Inconvénients
- Complexité de gestion de la chaîne : Il peut être difficile de maintenir une chaîne complexe avec de nombreux gestionnaires. Le contrôle du flux peut devenir difficile si la chaîne est trop longue ou si la hiérarchie n'est pas bien définie.
- Performance : Si la chaîne est très longue et que de nombreuses demandes sont transmises à travers plusieurs objets sans être traitées, cela peut affecter les performances.
- Pas de garantie de traitement : Une requête peut ne pas être traitée si la chaîne n'est pas bien configurée ou si aucun gestionnaire ne prend en charge cette requête.
- Dépendance entre les gestionnaires : Bien que chaque gestionnaire soit indépendant, ils dépendent les uns des autres en ce qui concerne l'ordre de la chaîne et la transmission des requêtes. Cela peut rendre difficile l'ajout de comportements spécifiques aux gestionnaires sans perturber la chaîne.
Decorator
Decorator est un patron de conception structurel qui permet d'ajouter de nouveaux comportements à un objet de manière flexible, sans modifier son code source. Le décorateur "enveloppe" l'objet d'origine et lui ajoute des fonctionnalités supplémentaires, tout en maintenant une interface commune avec l'objet de base.
Il est utilisé lorsqu'on souhaite étendre les fonctionnalités d'une classe de façon dynamique et modulaire, tout en respectant les principes de l'Open/Closed Principle (ou Principe d'Ouverture/Fermeture), qui stipule que les classes doivent être ouvertes à l'extension mais fermées à la modification.
Problème résolu
Le pattern Decorator résout le problème suivant : Comment ajouter de nouvelles fonctionnalités à un objet sans modifier sa classe ou avoir recours à l'héritage ?
Exemple typique du problème :
- Si on a une classe avec beaucoup de fonctionnalités et qu'on souhaite ajouter des comportements supplémentaires, on pourrait être tenté d'utiliser l'héritage pour ajouter ces fonctionnalités. Cependant, cela peut conduire à une explosion de sous-classes, ce qui devient difficile à gérer à mesure que les besoins augmentent (principe de la prolifération des sous-classes).
- Une autre approche pourrait être de modifier directement la classe de base, mais cela contrevient au principe de fermeture pour modification.
Structure du pattern Decorator
- Component (ou interface) : Définit l'interface que l'objet décoré et les décorateurs vont implémenter. Cela permet d'assurer que l'objet décoré et ses décorateurs sont traités de manière uniforme.
- ConcreteComponent : L'objet de base qui contient les fonctionnalités principales. C'est cet objet que l'on veut enrichir avec de nouvelles fonctionnalités.
- Decorator (ou classe abstraite) : C'est la classe qui encapsule l'objet Component et qui ajoute de nouvelles fonctionnalités sans modifier cet objet. Elle implémente l'interface Component.
- ConcreteDecorator : Ce sont les classes concrètes qui étendent le décorateur et ajoutent des fonctionnalités spécifiques.
Avantages
- Ajout dynamique de fonctionnalités : On peut ajouter des comportements supplémentaires à un objet sans modifier sa classe de base, ce qui permet d'étendre l'objet de manière dynamique.
- Flexibilité : Les objets peuvent être décorés de manière modulaire avec différents décorateurs, ce qui permet de combiner diverses fonctionnalités sans dupliquer du code ou créer un grand nombre de sous-classes.
- Respect du principe Open/Closed : Le pattern permet de quand même étendre les fonctionnalités des objets sans avoir besoin de modifier leur code existant (les objets sont ouverts à l'extension, mais fermés à la modification).
- Maintien d'une interface uniforme : Tous les objets décorés et les décorateurs implémentent la même interface, ce qui permet de les utiliser de manière interchangeable.
Inconvénients
- Complexité : Le nombre de classes peut augmenter rapidement, surtout si vous avez de nombreux types de décorateurs. Cela peut rendre le système plus complexe à comprendre et à gérer.
- Difficulté de gestion des décorateurs imbriqués : Lorsqu'il y a plusieurs niveaux de décoration, le débogage ou la compréhension du flux des appels peut devenir difficile, notamment si l'ordre des décorateurs est crucial.
- Perte de visibilité de l'objet de base : En ajoutant plusieurs décorateurs, l'objet sous-jacent peut devenir difficile à distinguer de ses décorations. Cela peut compliquer la gestion des objets dans des situations où la "pureté" de l'objet de base est nécessaire.
- Gestion de la responsabilité : Si un décorateur modifie des comportements de manière inattendue, cela peut affecter tous les objets décorés de manière similaire, et donc entraîner des effets secondaires non désirés.
Mediator
Médiateur est un patron de conception comportemental utilisé pour faciliter la communication entre différents objets ou classes sans qu'ils ne se réfèrent directement les uns aux autres. Cela permet de réduire les dépendances directes entre les composants d'un système, ce qui rend le système plus facile à maintenir et à étendre.
Explication Dans un système complexe, plusieurs objets ou classes peuvent interagir entre eux. Si chaque objet est directement connecté à tous les autres, le système devient rapidement difficile à gérer et à modifier (couplage élevé). Le médiateur agit comme un point central qui gère toutes les interactions entre les objets.
Chaque objet communique uniquement avec le médiateur, et non directement avec les autres objets. Avantages
- Réduction du couplage : Les objets ne dépendent pas directement les uns des autres. Ils dépendent uniquement du médiateur.
- Centralisation de la logique : La logique de communication est centralisée dans le médiateur, ce qui facilite la maintenance.
- Extensibilité : On peut ajouter de nouveaux collègues sans modifier les existants.
Inconvénients
- Complexité accrue :Le médiateur lui-même peut devenir complexe si le nombre d'interactions entre objets est très élevé.
- Monolithe potentiel :Si toutes les interactions passent par un seul médiateur, celui-ci peut devenir un goulot d'étranglement.
OOP paradigm
Le paradigme orienté objet est un modèle de programmation organisé autour des objets plutôt que des actions, et des données plutôt que de la logique. Il repose sur plusieurs concepts clés qui définissent son approche unique pour résoudre des problèmes et concevoir des systèmes
Conception OOP
Encapsulation L'encapsulation fait référence au regroupement des données (attributs) et des méthodes (fonctions ou opérations) qui opèrent sur ces données dans une seule unité appelée objet. Ce concept met l'accent sur l'idée de restreindre l'accès à l'état interne d'un objet et de n'exposer qu'une interface contrôlée au monde extérieur. Cela aide à maintenir une séparation claire entre ce que fait un objet et comment il le réalise.
Abstraction L'abstraction implique de cacher les réalités complexes tout en exposant uniquement les parties nécessaires. Elle permet aux programmeurs de se concentrer sur les interactions à un niveau supérieur sans avoir besoin de comprendre les détails complexes des opérations de niveau inférieur. Cela est réalisé en définissant des types de données abstraits ou des interfaces qui encapsulent un ensemble de comportements et de propriétés.
Héritage L'héritage est un mécanisme qui permet à une classe (la sous-classe ou classe dérivée) d'hériter des attributs et méthodes d'une autre classe (la superclasse ou classe de base). Cette fonctionnalité prend en charge la création de nouvelles abstractions basées sur des abstractions existantes et favorise la réutilisation du code.
Polymorphisme Le polymorphisme permet à des objets de différentes classes d'être traités comme des objets d'une superclasse commune. Il permet à une interface unique de représenter différentes formes sous-jacentes (types de données). Il existe deux types principaux de polymorphisme : le polymorphisme de compilation (ou statique), réalisé par le surchargement de méthodes, et le polymorphisme d'exécution (ou dynamique), réalisé par la redéfinition de méthodes.
Heritage
L'héritage est une relation entre une classe enfant (sous-classe) hérite des attributs et méthodes d'une classe parent (superclasse). L'héritage permet à une classe d'étendre une autre classe.
*Caractéristiques* Permet la réutilisation du code en héritant des fonctionnalités d'une classe existante. Soutient le polymorphisme, permettant à une sous-classe d'être utilisée à la place de sa superclasse. (Peut conduire à un couplage fort et à une hiérarchie de classes rigide, rendant le code difficile à modifier et à maintenir.)Exemple : Considérons une classe Voiture héritant d'une superclasse Véhicule. La classe Voiture hérite des attributs et méthodes de Véhicule et peut également avoir ses propres attributs et méthodes spécifiques.
Composition
La composition est une forme plus stricte d'agrégation avec une relation parent-enfant où les objets contenus sont liés à la durée de vie de l'objet conteneur. Cela signifie que si l'objet conteneur est détruit, les objets contenus le seront également.
Caractéristiques La relation est souvent décrite comme "fait partie de". Les objets contenus sont exclusifs à l'objet conteneur et ne peuvent pas être partagés. La destruction de l'objet conteneur entraîne la destruction des objets contenus. *Exemple* Un livre et ses pages. Les pages d'un livre n'ont pas de sens en dehors du livre lui-même, et si le livre est détruit, les pages le sont également.
Différences entre Agrégation et Composition *Lien de propriété* En agrégation, les objets contenus peuvent survivre indépendamment de l'objet conteneur, tandis qu'en composition, les objets contenus font intrinsèquement partie de l'objet conteneur et leur durée de vie est liée. *Couplage* La composition représente un couplage plus fort entre les objets conteneur et contenus, indiquant une dépendance plus forte entre eux par rapport à l'agrégation. *Partage* En agrégation, un objet contenu peut être partagé entre plusieurs conteneurs. En composition, un objet contenu appartient exclusivement à un seul conteneur.
Abstraction
L'abstraction est un processus consistant à masquer les détails de la mise en œuvre et à afficher uniquement les fonctionnalités de l'utilisateur. Par exemple, l'envoi de SMS à l'endroit où vous tapez le texte et envoyez le message. Vous ne connaissez pas le traitement interne relatif à la livraison du message. L'abstraction vous permet de vous concentrer sur ce que fait l'objet au lieu de savoir comment il le fait.
Deux façons de réaliser l'abstraction Classe abstrait Interface
Interface
Une interface est un type de référence similaire à une classe, qui est une collection de méthodes abstraites. Une interface peut contenir des méthodes abstraites et des constantes, mais pas de méthodes concrètes (sauf les méthodes par défaut et statiques ajoutées dans Java 8 et ultérieures). Les interfaces sont utilisées pour spécifier un contrat que les classes implémentant l'interface doivent suivre, définissant un ensemble de comportements que les classes doivent implémenter.
Aggregation OOP
L'agrégation est une relation qui représente une association entre deux classes où les objets de la classe conteneur peuvent exister indépendamment des objets contenus. Dans une relation d'agrégation, la durée de vie des objets contenus n'est pas gérée par l'objet conteneur.
Caractéristiques La relation est souvent décrite comme "a un(e)" ou "possède un(e)". Les objets contenus peuvent appartenir à plusieurs objets conteneurs. La destruction de l'objet conteneur n'entraîne pas la destruction des objets contenus. Exemple : Un département d'une université et ses professeurs. Un professeur peut exister sans être associé à un département, et sa suppression ne supprime pas le département auquel il est associé.
Abstract VS Interface
Classe Abstraite Une classe abstraite est une classe qui ne peut pas être instanciée. Elle peut contenir à la fois des méthodes abstraites (sans corps) et des méthodes concrètes (avec une implémentation). *Héritage* Une classe peut hériter d'une seule classe abstraite, ce qui limite son utilisation. *Utilisation* Les classes abstraites sont souvent utilisées lorsque certaines méthodes communes devraient être implémentées et partagées parmi toutes les sous-classes, mais que d'autres méthodes doivent rester abstraites (et donc être implémentées par les sous-classes).
Interface Une interface est un type de référence en Java qui est 100% abstrait. Avant Java 8, elle ne pouvait contenir que des méthodes abstraites (sans corps). Depuis Java 8, elle peut également contenir des méthodes par défaut (avec une implémentation) et des méthodes statiques. *Héritage* Une classe peut implémenter plusieurs interfaces, ce qui permet de contourner la limitation de l'héritage simple de Java et de faciliter une forme d'héritage multiple. *Méthodes et champs* Toutes les variables définies dans les interfaces sont publiques, statiques et finales par définition. Les méthodes dans les interfaces sont abstraites par défaut, mais peuvent être déclarées comme statiques ou par défaut depuis Java 8. *Constructeurs* Les interfaces ne peuvent pas avoir de constructeurs car elles ne peuvent pas être instanciées par elles-mêmes. *Utilisation* Les interfaces sont utilisées pour spécifier un contrat que les classes implémentantes doivent suivre, sans imposer la manière dont les méthodes doivent être implémentées. Elles sont utiles pour définir un ensemble de méthodes qui peuvent être implémentées par différentes classes, potentiellement sans lien de hiérarchie direct.
Comparaison et choix entre Classe Abstraite et Interface *Conception* Utilisez une classe abstraite quand on veut partager du code entre plusieurs classes étroitement liées (familles de classes). Utilisez une interface lorsque on veut spécifier un contrat pour des classes qui ne sont pas liées entre elles. *Flexibilité* Les interfaces offrent une plus grande flexibilité grâce à la capacité d'implémenter plusieurs interfaces dans une seule classe.
Heritage Vs Composite
*Type de relation* L'héritage établit une relation "est un(e)", où la sous-classe est une spécialisation de la superclasse. La composition établit une relation "a un(e)", où une classe utilise la fonctionnalité d'une ou plusieurs autres classes en les incluant comme instances. *Couplage* L'héritage peut conduire à un couplage fort et à une hiérarchie de classes rigide, tandis que la composition permet une conception plus modulaire et flexible avec un couplage plus faible. *Flexibilité* La composition est plus flexible que l'héritage, car elle permet de modifier dynamiquement les comportements en composant des objets à l'exécution, tandis que l'héritage est statique et défini à la compilation. *Substituabilité* L'héritage soutient le principe de substitution de Liskov, permettant à une instance de la sous-classe d'être utilisée à la place de l'instance de la superclasse. La composition ne soutient pas directement ce principe.
En résumé, bien que l'héritage et la composition soient tous deux utilisés pour réutiliser le code, ils le font de manières différentes et ont des implications distinctes pour la conception de ton système. La composition est souvent recommandée pour sa flexibilité et son faible couplage, tandis que l'héritage est utile pour modéliser des relations hiérarchiques claires entre les classes.
Référence Vs par valeur
Passage par valeur: signifie qu'on passe une copie d'un objet en tant que paramètre dans une méthode.Les modifications apportées à cette valeur n'affectent pas la valeur originale à l'extérieur de la fonction. C'est pour les types primitifs comme int, double, char, etc.
Passage par référence signifie qu'on passe d' une référence à un objet en tant que paramètre dans une méthode. Ce qui est transmis à la fonction n'est pas une copie de la valeur, mais une référence (ou un pointeur) vers l'emplacement de mémoire. Cela signifie que les modifications apportées à l'objet à l'intérieur de la fonction affectent l'objet original à l'extérieur. C'est pour les objets non-primitifs (comme les instances de classes, les tableaux, etc.)
Overloading Vs Overriding
Overloading Overloading se produit au sein d'une même classe lorsque deux ou plusieurs méthodes partagent le même nom mais diffèrent par leurs listes de paramètres (type, nombre ou ordre des paramètres).
Redéfinition (Overriding) La redéfinition de méthodes se produit lorsque une sous-classe fournit une implémentation spécifique pour une méthode héritée de la classe parent. La méthode dans la sous-classe doit avoir la même signature, le même type de retour (ou un sous-type) que la méthode dans la classe parent.
*Liste des méthodes ne peuvent pas être redéfinies* Méthodes statiques Méthodes finales Constructeurs Méthodes privées Méthodes dans les classes finales
Différences clés *Contexte* Le surchargement permet à une classe d'avoir plusieurs méthodes du même nom mais avec des paramètres différents, tandis que la redéfinition permet à une sous-classe de fournir une implémentation spécifique pour une méthode héritée. *Signature* Pour le surchargement, les méthodes doivent différer par leurs signatures. Pour la redéfinition, la signature doit rester la même.
Inner VS Nested class
Inner Class (Classe interne) Une inner class est une classe définie à l'intérieur d'une autre classe. Une instance d'une inner class est toujours associée à une instance de la classe englobante. Accès Une inner class a accès aux membres de la classe englobante, y compris ceux déclarés comme private. Elle peut directement accéder aux méthodes et aux attributs de l'instance de la classe englobante. Utilisation Les inner classes sont utiles lorsqu'on souhaite définir une classe qui ne sera utilisée que dans le contexte de la classe englobante, pour gérer les événements de cette classe ou pour fournir un support à des opérations spécifiques à la classe englobante. Instantiation Pour créer une instance d'une inner class, on doit d'abord créer une instance de la classe englobante. Par exemple : ClasseExterieure.ClasseInterne objetInterne \= objetExterieur.new ClasseInterne(); L'utilisation du signe $ entre la classe principale et la classe interne permet d'éviter des confusions de nom entre le nom d'une classe appartenant à un package et le nom d'une classe interne. Pour pouvoir utiliser une variable de classe dans une classe interne, il faut la déclarer dans sa classe englobante. Il existe quatre types de classes internes :
- les classes internes non statiques : elles sont membres à part entière de la classe qui les englobe et peuvent accéder à tous les membres de cette dernière
- les classes internes locales : elles sont définies dans un bloc de code. Elles peuvent être static ou non.
- les classes internes anonymes : elles sont définies et instanciées à la volée sans posséder de nom
- les classe internes statiques : elles sont membres à part entière de la classe qui les englobe et peuvent accéder uniquement aux membres statiques de cette dernière
Static Nested Class (Classe imbriquée statique) Une static nested class est une classe statique définie à l'intérieur d'une autre classe. Elle est statique dans le sens où elle n'a pas besoin d'une instance de la classe englobante pour être instanciée. Accès Une static nested class ne peut accéder qu'aux membres statiques de la classe englobante. Elle ne peut pas accéder directement aux membres d'instance de la classe englobante. Utilisation : Les static nested classes sont utilisées lorsqu'on a besoin d'une classe qui est étroitement liée à la classe englobante, mais qui n'a pas besoin d'accéder à l'état d'une instance de la classe englobante. Elles sont souvent utilisées pour regrouper des classes qui seront utilisées comme types auxiliaires.
Instantiation : Une static nested class peut être instanciée sans créer une instance de la classe englobante. Par exemple : ClasseExterieure.ClasseImbriqueeStatique objetStatique \= new ClasseExterieure.ClasseImbriqueeStatique();
This Vs super
Contexte : this est utilisé dans le contexte de l'objet courant pour accéder à ses membres ou pour appeler ses autres constructeurs. super, est utilisé pour accéder ou appeler les membres de la superclasse, lorsqu'ils sont masqués ou redéfinis dans la classe courante. Appel de Constructeur : this() et super() sont utilisés dans les constructeurs pour appeler d'autres constructeurs au sein de la même classe ou dans la superclasse..
Couplage faible Vs fort
Couplage fort: Un objet fortement couplé est un objet qui a besoin de connaître les autres objets et est généralement très dépendant les uns des autres. La modification d'un objet dans une application fortement couplée nécessite souvent de modifier d'autres objets.
Couplage faible: Le couplage faible permet de réduire les interdépendances entre les composants d'un système dans le but de réduire le risque que les changements dans un composant nécessitent des changements dans tout autre composant. Le couplage faible est un concept destiné à augmenter la flexibilité du système, à le rendre plus maintenable et à rendre l'ensemble du framework plus stable.
Static
Static(class variable): indique que l'élément appartient à la classe elle-même, plutôt qu'à une instance de la classe. C'est -a-dire qu'on peut accéder à un membre statique sans avoir besoin de créer une instance de la classe.
- Pour les variables : une variable static conserve sa valeur entre plusieurs instances de la classe.
- Pour les méthodes :elle peut être invoquée sans créer une instance de la classe. Elle ne peut accéder qu'aux membres statiques de la classe.
Final
Final: Le modificateur final peut être utilisé avec des classes, des méthodes et des variables et à différents dans chaque cas :
- Pour les classes : une classe déclarée comme final ne peut pas être héritée.
- Pour les méthodes : elle ne peut pas être surchargée dans une sous-classe.
- Pour les variables : une variable final ne peut être assignée qu'une fois, soit lors de sa déclaration, soit dans le constructeur. Elle est constante.
Private
Private: assure que les membres d'une classe ne seront pas accessibles en dehors de cette classe. Il peut être appliqué à des méthodes, propriétés, constructeurs, classes imbriquées, mais pas aux classes de niveau supérieur elles-mêmes.
Default ou package-private
default ou package-private: Si aucun modificateur d'accès n'est spécifié, Java utilise le modificateur par défaut, qui n'a pas de mot-clé spécifique. Les membres déclarés sans modificateur d'accès ne sont accessibles que dans les classes du même package.
Public
public: rend le membre accessible de n'importe où dans le programme. Si une classe, une méthode ou une variable est déclarée comme public, elle peut être accédée depuis toute autre classe ou package du programme.
Clean Architecture
L'Architecture Clean est un modèle d'architecture qui met l'accent sur la séparation des préoccupations afin de rendre la base de code plus facile à maintenir, à tester et à étendre. Elle repose sur quatre couches :
- la couche de présentation, chargée de gérer les interactions avec l'utilisateur,
- la couche d'application, chargée de gérer la logique commerciale,
- la couche de domaine, chargée de définir les entités commerciales et leurs relations,
- la couche d'infrastructure, chargée de gérer les préoccupations externes telles que les bases de données et les services web.
Les principes clés de la Clean Architecture sont l'indépendance des couches, le flux de données des couches externes vers les couches internes et la possibilité de remplacer des dépendances sans affecter le reste du système.
Structure du projet La structure de projet de Clean Architecture suit un schéma structuré qui permet la réutilisation et la testabilité du code. Dans la structure du projet, vous avez généralement un projet pour la couche de présentation, un projet pour la couche d'application, un projet pour la couche de domaine, un projet pour la couche d'infrastructure et un projet pour les tests. Chaque couche possède son propre ensemble d'interfaces et d'implémentations, la couche d'application servant de pont entre les autres couches.
Architecture hexagonale
L'architecture hexagonale, également connue sous le nom d'architecture de ports et d'adaptateurs ou de modèle de ports et d'adaptateurs, est un modèle d'architecture qui sépare la logique business de base de l'infrastructure et des systèmes externes. Dans l'architecture hexagonale, l'application est divisée en trois composants principaux :
- le composant central contient la logique business et constitue le cœur de l'application,
- les adaptateurs sont responsables de la communication avec les systèmes externes, tels que les bases de données ou les services web,
- les ports sont les interfaces entre le noyau et les adaptateurs, et ils définissent les opérations que le noyau peut effectuer.
Structure du projet Dans l'architecture hexagonale, la structure du projet est divisée en quatre couches principales : le noyau, les adaptateurs, les ports et les tests. La couche du noyau contient le modèle du domaine et la logique business, tandis que la couche des adaptateurs contient les implémentations des interfaces définies dans la couche des ports. La couche des tests contient les tests de l'application.
Comparaison Hexagonal vs Clean
L'Architecture propre et l'Architecture hexagonale ont des objectifs similaires, à savoir créer des systèmes logiciels maintenables, testables et extensibles, mais elles diffèrent dans leur approche de la structuration du projet et dans la manière dont elles atteignent ces objectifs. L'architecture propre met l'accent sur la séparation des préoccupations, tandis que l'architecture hexagonale met l'accent sur la séparation de la logique métier centrale de l'infrastructure et des systèmes externes.
Avantages et inconvénients La Clean Architecture est un bon choix pour les projets qui nécessitent une séparation claire des préoccupations et qui ont une logique d'entreprise complexe. Cependant, elle peut être plus difficile à mettre en œuvre que d'autres modèles d'architecture.
L'architecture hexagonale est un bon choix pour les projets qui nécessitent un haut degré de flexibilité et d'adaptabilité. Elle est également plus facile à tester que les autres modèles d'architecture. Cependant, elle peut être plus complexe que d'autres modèles d'architecture et n'est peut-être pas le meilleur choix pour les petits projets.
GIT
Toute la branche feature sera ainsi déplacée sur la pointe de la branche master, et tous les nouveaux commits seront intégrés à master.
Rebase consiste à réécrire l'historique du projet en créant de nouveaux commits pour chaque commit de la branche d'origine.
git rebase -i HEAD~3 : Vous devez indiquer jusqu'à quand remonter dans votre historique en donnant à la commande le *commit* sur lequel vous voulez vous rebase
Merge
Un nouveau « commit de merge » est créé dans la branche feature. Il relie les historiques des deux branches.