Introduction aux Design Patterns
Solutions éprouvées pour des problèmes récurrents
Les design patterns sont des solutions réutilisables à des problèmes
courants de conception logicielle.
Ils représentent les meilleures pratiques utilisées par les développeurs
expérimentés.
Cette présentation explore les patterns les plus importants et leur
application pratique.
Qu'est-ce qu'un Design Pattern ?
Définition : Un design pattern est une solution réutilisable à un problème récurrent dans la conception de logiciels. Le
concept a été popularisé par le livre "Design Patterns : Elements of Reusable Object-Oriented Software" du Gang of
Four (GoF).
Pourquoi les utiliser ?
Réutilisabilité : Vous n'avez pas besoin de réinventer la roue pour
chaque problème.
Maintenabilité : Le code devient plus facile à comprendre, à modifier
et à maintenir.
Extensibilité : Les patterns facilitent l'ajout de nouvelles
fonctionnalités sans modifier le code existant.
Vocabulaire commun : Ils offrent un langage partagé pour les
développeurs, simplifiant les discussions sur l'architecture logicielle.
Les principes SOLID
Les Design Patterns sont étroitement liés aux principes SOLID, qui sont un ensemble de cinq principes de conception qui visent à rendre le code plus compréhensible,
flexible et maintenable.
S Single Responsibility Principle
Une classe doit avoir une seule raison de changer.
O Open/Closed Principle
Les classes doivent être ouvertes à l'extension mais fermées à la modification.
L Liskov Substitution Principle
Les objets d'un programme doivent pouvoir être remplacés par des instances de leurs
sous-classes sans altérer le bon fonctionnement du programme.
I Interface Segregation Principle
Les clients ne doivent pas être forcés de dépendre d'interfaces qu'ils n'utilisent pas.
D Dependency Inversion Principle
Les Patterns Créationnels
Les patterns créationnels sont des solutions qui traitent des mécanismes de création d'objets, en
essayant de créer des objets de manière adaptée à la situation. Ils abstraient le processus
d'instanciation et aident à rendre un système indépendant de la façon dont ses objets sont créés.
Utilités principales
Encapsuler la connaissance : Ils encapsulent la connaissance à
propos des classes concrètes utilisées par le système.
Masquer la complexité : Ils cachent comment les instances de ces
classes sont créées et assemblées.
Flexibilité : Ils permettent de configurer un système avec différentes
familles d'objets.
Singleton Factory Method Builder
Garantit qu'une classe n'a qu'une
seule instance et fournit un point Définit une interface pour créer Sépare la construction d'un objet
d'accès global à cette instance. un objet, mais laisse aux sous- complexe de sa représentation
classes le soin de décider quelle pour créer différentes
classe instancier. représentations.
Prototype Abstract Factory
Permet de copier des objets Fournit une interface pour créer
existants sans que votre code des familles d'objets liés sans
dépende de leurs classes. spécifier leurs classes concrètes.
Pattern Singleton
Définition
Le Singleton est un pattern de conception créationnel qui garantit qu'une classe n'a qu'une seule
et unique instance et fournit un point d'accès global à cette instance.
Problème
Vous avez besoin qu'une classe n'ait qu'une seule instance disponible pour tous les clients, par
exemple, un objet unique de connexion à une base de données.
Solution
Rendre le constructeur privé et fournir une méthode statique qui retourne l'instance unique, en
la créant la première fois si elle n'existe pas. classe DatabaseConnection {
privé statique instance: DatabaseConnection
Applications privé constructeur() { /* initialisation */ }
Gestionnaires de logs Connexions à une base de données public statique méthode getInstance() {
si (instance == null) {
Fichiers de configuration instance = new DatabaseConnection()
}
retourner instance
}
}
Pattern Factory Method
Définition
Le Factory Method est un pattern créationnel qui fournit une interface pour la création d'objets
dans une super-classe, mais laisse aux sous-classes le soin de modifier le type des objets créés.
Problème
Une classe ne peut pas anticiper la classe des objets qu'elle doit créer. Par exemple, une
application de logistique peut avoir besoin de créer différents types de transport (Camion, classe abstraite TransportCreator {
public abstraite méthode createTransport(): Transport
Bateau).
public fonction operate() {
produit = createTransport()
Solution [Link]()
}
}
Remplacer les appels directs au constructeur par des appels à une méthode spéciale, la
"méthode de fabrique". Les sous-classes peuvent surcharger cette méthode. classe RoadLogistics extends TransportCreator {
public méthode createTransport(): Transport {
retourner new Truck()
Applications }
}
Dans un framework, pour permettre au code client d'étendre ses composants
Pour fournir aux utilisateurs d'une bibliothèque un moyen d'étendre ses composants
Pattern Builder
Définition
Le Builder est un pattern de conception créationnel qui permet de construire des objets
complexes étape par étape. Il permet de produire différentes représentations d'un objet en
utilisant le même code de construction.
Problème
Un constructeur avec une longue liste de paramètres devient ingérable. La plupart de ces
paramètres peuvent être inutilisés, rendant les appels au constructeur pénibles.
Solution
Extraire le code de construction de l'objet de sa propre classe et le déplacer dans des objets
séparés appelés "builders". Le processus de construction est décomposé en plusieurs étapes.
Applications
Construction d'objets complexes comme des requêtes SQL ou des documents XML
Création d'objets avec de nombreux paramètres optionnels
Pattern Prototype
Définition
Le Prototype est un pattern de conception créationnel qui permet de copier des objets existants
sans que votre code dépende de leurs classes.
Problème
Vous devez créer une copie exacte d'un objet, mais vous ne pouvez pas accéder directement à
son constructeur ou sa structure interne est complexe.
Solution
Le pattern Prototype délègue le processus de clonage aux objets qui sont clonés. Il déclare une interface Prototype {
interface commune pour tous les objets qui prennent en charge le clonage, permettant de copier méthode clone(): Prototype
un objet sans connaître sa classe concrète. }
classe ConcretePrototype implements Prototype {
privé field1: string
Applications privé field2: number
Création d'objets coûteux à initialiser constructeur(prototype) {
this.field1 = prototype.field1
Création d'objets avec des états initiaux similaires this.field2 = prototype.field2
}
public méthode clone(): Prototype {
retourner new ConcretePrototype(this)
}
}
Pattern Fabrique Abstraite
Définition
La Fabrique Abstraite est un pattern de conception créationnel qui permet de créer des familles
d'objets apparentés sans préciser leurs classes concrètes.
Problème
Comment créer des familles d'objets liés ou dépendants sans spécifier leurs classes concrètes et
en garantissant que les objets créés sont compatibles entre eux ?
Solution
interface AbstractFactory {
Déclarer une interface pour chaque type d'objet produit, puis créer une interface de fabrique méthode createProductA(): AbstractProductA
abstraite qui déclare des méthodes pour créer chaque type d'objet. Enfin, implémenter des méthode createProductB(): AbstractProductB
fabriques concrètes pour chaque variante de famille de produits. }
classe ConcreteFactory1 implements AbstractFactory {
public méthode createProductA(): AbstractProductA {
Applications retourner new ConcreteProductA1()
}
Création de familles de produits compatibles public méthode createProductB(): AbstractProductB {
retourner new ConcreteProductB1()
Bibliothèques d'interface utilisateur multi-plateformes }
}
Les Patterns Structurels
Les patterns structurels sont des solutions réutilisables qui expliquent comment assembler
différentes classes et objets pour former des structures plus grandes et plus flexibles.
Utilités principales
Réduire la complexité : Ils aident à gérer de grands systèmes en les
découpant en morceaux plus simples.
Faciliter la maintenance : L'impact des modifications sur le reste du
système est minimisé.
Améliorer la flexibilité : Adapter le comportement sans modifier le
code existant.
Adapter Decorator Facade
Fournit une interface simplifiée à
Permet à des objets ayant des Ajoute dynamiquement des
un ensemble complexe de
interfaces incompatibles de responsabilités à un objet.
classes.
collaborer.
Bridge Composite Flyweight
Réduit la consommation de
Sépare une abstraction de son Compose des objets en structures
mémoire en partageant les
implémentation pour qu'elles arborescentes pour représenter
données communes entre
puissent varier indépendamment. des hiérarchies partie-tout.
plusieurs objets.
Pattern Adapter
Définition
L'Adapter est un pattern de conception structurel qui permet à des objets ayant des interfaces
incompatibles de collaborer.
Problème
Vous voulez utiliser une classe existante, mais son interface n'est pas compatible avec le reste de
votre code.
Solution
Créer une classe "adaptateur" qui s'intercale entre votre application et la classe incompatible.
Cet adaptateur implémente l'interface attendue par votre code.
interface NewApi {
méthode displayInformation()
}
Applications
classe OldApi {
Intégration de bibliothèques tierces public méthode showData() { ... }
}
Réutilisation de sous-classes existantes
classe ApiAdapter implements NewApi {
privé adaptee: OldApi
public méthode displayInformation() {
[Link]()
}
}
Pattern Decorator
Définition
Le Decorator est un pattern de conception structurel qui permet d'ajouter dynamiquement de
nouveaux comportements à des objets sans modifier leur classe.
Problème
Vous souhaitez pouvoir ajouter des responsabilités supplémentaires à un objet de manière
dynamique et transparente, sans affecter les autres objets.
Solution
Placer l'objet cible dans un autre objet "enveloppeur" (wrapper) qui imite l'interface de l'objet
cible. Ce wrapper peut ensuite ajouter son propre comportement avant ou après avoir délégué
l'appel à l'objet enveloppé.
interface Coffee {
Applications méthode getCost(): number
méthode getDescription(): string
Ajout de fonctionnalités à des composants graphiques }
Classes de flux de données (FileInputStream, BufferedInputStream) classe SimpleCoffee implements Coffee { ... }
classe CoffeeDecorator implements Coffee {
protégé wrappee: Coffee
public méthode getCost() {
retourner [Link]()
}
}
Pattern Facade
Définition
La Façade est un pattern de conception structurel qui fournit une interface simplifiée à une
bibliothèque, un framework ou tout autre ensemble complexe de classes.
Problème
Vous avez besoin de travailler avec un sous-système complexe qui comporte de nombreuses
classes et dépendances. Interagir directement avec ce sous-système est compliqué et source
d'erreurs.
Solution
classe VideoSystem { méthode turnOn() { ... } }
classe AudioSystem { méthode setVolume(level) { ... } }
Créer une classe Facade qui offre une interface simple et limitée au sous-système. La façade classe LightSystem { méthode dim(level) { ... } }
reçoit les appels du client et délègue le travail aux classes appropriées du sous-système, cachant
ainsi sa complexité. classe HomeCinemaFacade {
privé video: VideoSystem
privé audio: AudioSystem
Applications privé lights: LightSystem
Fournir une interface simple à un sous-système complexe public méthode watchMovie() {
[Link](10)
Découpler le code client des composants internes d'un sous-système [Link]()
[Link](5)
}
}
Pattern Bridge (Pont)
Définition
Le Bridge est un pattern de conception structurel qui permet de séparer une abstraction de son
implémentation afin que les deux puissent varier indépendamment.
Problème
Comment éviter un couplage permanent entre une abstraction et son implémentation ?
Comment permettre à l'abstraction et à l'implémentation de varier indépendamment ?
Solution
Séparer l'abstraction de son implémentation en plaçant ces deux concepts dans des hiérarchies
de classes distinctes. L'abstraction contient une référence à l'implémentation et délègue le travail
à cet objet d'implémentation.
classe Abstraction {
protégé implementor: Implementor
Applications constructeur(implementor: Implementor) {
[Link] = implementor
Éviter un lien permanent entre abstraction et implémentation }
Étendre une classe dans plusieurs dimensions orthogonales public méthode operation(): void {
[Link]()
}
}
interface Implementor {
méthode operationImpl(): void
}
Pattern Composite
Définition
Le Composite est un pattern de conception structurel qui permet de composer des objets en
structures arborescentes pour représenter des hiérarchies partie-tout et traiter les objets
individuels et les compositions d'objets de manière uniforme.
Problème
Comment représenter des hiérarchies d'objets partie-tout ? Comment permettre aux clients de
traiter de manière uniforme les objets individuels et les compositions d'objets ?
Solution
Définir une interface commune pour tous les objets de la composition (composants simples et
composites). Les composites délèguent le travail à leurs composants enfants et traitent les
résultats intermédiaires. interface Component {
méthode operation(): void
}
Applications classe Leaf implements Component {
public méthode operation(): void {
Structures arborescentes (interfaces graphiques, documents) // Opération sur une feuille
}
Opérations récursives sur des structures hiérarchiques }
classe Composite implements Component {
privé children: List
public méthode add(component: Component): void { ... }
public méthode remove(component: Component): void { ... }
public méthode operation(): void {
pour chaque child dans children {
Pattern Flyweight (Poids Mouche)
Définition
Le Flyweight est un pattern de conception structurel qui permet de réduire la consommation de
mémoire en partageant les données communes entre plusieurs objets similaires.
Problème
Comment supporter efficacement un grand nombre d'objets fins qui partagent une partie de leur
partie de leur état, tout en minimisant la consommation de mémoire ?
Solution
Séparer l'état intrinsèque (partagé) de l'état extrinsèque (unique). Stocker l'état intrinsèque dans
intrinsèque dans des objets partagés (flyweights) et passer l'état extrinsèque aux méthodes qui
méthodes qui l'utilisent.
Applications
Éditeurs de texte (partage des caractères)
Jeux vidéo (partage des textures, modèles 3D)
classe Flyweight {
privé sharedState: any
constructeur(sharedState: any) {
[Link] = sharedState
Les Patterns Comportementaux
Les patterns comportementaux sont des solutions qui se concentrent sur la manière dont les
manière dont les objets et les classes communiquent et s'organisent pour accomplir une tâche.
une tâche. Plutôt que de s'intéresser à la structure des objets, ils gèrent leurs interactions et la
interactions et la distribution des responsabilités.
Utilités principales
Améliorer la communication : Ils définissent des schémas clairs
pour la communication entre les objets.
Faciliter l'ajout de fonctionnalités : En découplant les objets, on
peut modifier leurs interactions sans toucher au code existant.
Rendre le code plus maintenable : La séparation des responsabilités
rend chaque partie du code plus simple.
Observer Strategy Iterator
Définit un mécanisme de
souscription pour notifier Transforme un ensemble de Permet de parcourir les éléments
plusieurs objets des événements comportements en objets et les d'une collection sans exposer sa
qui arrivent à l'objet qu'ils rend interchangeables à représentation sous-jacente.
observent. l'intérieur de l'objet de contexte
original.
Mediator Visitor
Pattern Observer
Définition
L'Observer est un pattern de conception comportemental qui permet de définir un mécanisme
de souscription pour notifier plusieurs objets des événements qui arrivent à l'objet qu'ils
observent.
Problème
Un objet (le sujet) doit notifier d'autres objets (les observateurs) de ses changements d'état, mais
sans être couplé aux classes concrètes de ces observateurs.
Solution
Le pattern Observer propose que l'objet "Sujet" maintienne une liste de ses "Observateurs".
Lorsqu'un changement d'état se produit, le sujet parcourt sa liste et appelle une méthode de
notification (update()) sur chaque observateur.
Applications
Systèmes de gestion d'événements (GUI, frameworks web)
Architecture Modèle-Vue-Contrôleur (MVC)
interface Subject {
méthode attach(observer: Observer)
méthode detach(observer: Observer)
méthode notify()
Pattern Strategy
Définition
La Stratégie est un pattern de conception comportemental qui transforme un ensemble de
ensemble de comportements en objets et les rend interchangeables à l'intérieur de l'objet de
l'objet de contexte original.
Problème
Vous avez une classe qui peut faire quelque chose de plusieurs manières différentes (en utilisant
(en utilisant différents algorithmes), et vous voulez pouvoir changer d'algorithme à l'exécution.
l'exécution.
Solution interface PaymentStrategy {
méthode pay(amount: number)
Extraire tous les algorithmes pour les placer dans des classes séparées appelées "stratégies". La }
"stratégies". La classe originale, appelée "contexte", contient une référence vers l'une des
classe CreditCardStrategy implements PaymentStrategy { ... }
l'une des stratégies et lui délègue le travail.
classe PaypalStrategy implements PaymentStrategy { ... }
classe PaymentContext {
Applications privé strategy: PaymentStrategy
Implémentation de différents algorithmes de tri ou de compression public méthode setStrategy(strategy: PaymentStrategy) {
[Link] = strategy
Gestion de différentes règles de validation ou de systèmes de paiement }
public méthode executePayment(amount: number) {
[Link](amount)
}
}
Pattern Iterator (Itérateur)
Définition
L'Itérateur est un pattern de conception comportemental qui permet de parcourir les éléments
d'une collection sans exposer sa représentation sous-jacente (liste, pile, arbre, etc.).
Problème
Comment accéder séquentiellement aux éléments d'une collection sans exposer sa structure
interne ? Comment permettre plusieurs parcours simultanés et indépendants d'une même
collection ?
Solution
interface Iterator
Extraire le comportement de parcours d'une collection dans un objet séparé appelé itérateur. En {
plus de l'implémentation du parcours, l'itérateur encapsule tous les détails du parcours. méthode hasNext(): boolean
méthode next(): T
}
Applications interface Aggregate {
méthode createIterator(): Iterator
Parcours de collections complexes }
Parcours simultanés d'une même collection
Pattern Mediator (Médiateur)
Définition
Le Médiateur est un pattern de conception comportemental qui réduit les dépendances
chaotiques entre objets en les forçant à communiquer indirectement, via un objet médiateur.
Problème
Comment réduire le couplage entre des objets qui doivent communiquer entre eux ? Comment
? Comment éviter que les objets se référencent explicitement les uns les autres ?
Solution interface Mediator {
méthode notify(sender: Component, event: string): void
Définir un objet médiateur qui encapsule les interactions entre un ensemble d'objets. Les objets }
d'objets. Les objets ne communiquent plus directement entre eux, mais via le médiateur, ce qui
médiateur, ce qui réduit les dépendances. classe ConcreteMediator implements Mediator {
privé componentA: ComponentA
privé componentB: ComponentB
Applications
constructeur(componentA, componentB) {
Interfaces utilisateur (communication entre composants) [Link] = componentA
[Link] = componentB
Systèmes de contrôle du trafic aérien
[Link](this)
[Link](this)
}
public méthode notify(sender, event) {
if (event === "A") {
[Link]()
}
if (event === "B") {
Pattern Visitor (Visiteur)
Définition
Le Visiteur est un pattern de conception comportemental qui permet de séparer les algorithmes
des objets sur lesquels ils opèrent, permettant d'ajouter de nouvelles opérations sans modifier
les classes des objets.
Problème
Comment définir une nouvelle opération sur une structure d'objets sans changer les classes de
ces objets ? Comment ajouter de nouvelles fonctionnalités à une hiérarchie de classes sans la
modifier ?
Solution
Placer le nouvel algorithme dans une classe séparée appelée visiteur. Les objets originaux
interface Visitor {
doivent seulement fournir une méthode "accept" qui accepte un visiteur et appelle la méthode méthode visitElementA(element: ElementA): void
spécifique du visiteur correspondant à la classe de l'objet. méthode visitElementB(element: ElementB): void
}
interface Element {
Applications méthode accept(visitor: Visitor): void
}
Opérations sur des structures d'objets complexes
classe ConcreteElementA implements Element {
Parseurs et compilateurs public méthode accept(visitor: Visitor): void {
[Link](this)
}
public méthode operationA(): void { ... }
}
classe ConcreteVisitor implements Visitor {