Design Patterns
C’est quoi ?
Les patterns offrent des solutions à des problèmes récurrents rencontrés par les développeurs, Ils permettent de :
- Limiter le couplage
- Faciliter la maintenance
- Être moins rigide face au changement
Ils sont indispensables à connaître pour le développeur :
- Pour s’assurer d’une meilleure conception
- Pour dialoguer avec les développeurs (à travers le “langage pattern”)
- Pour comprendre les frameworks
Un livre écrit par le GoF (Gang of Four : Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) décrit 23 Design Patterns appliqués à la conception orientée objet, classés en trois catégories :
Creational patterns
Les patterns de création, relatifs à la création d’objet.
Singleton
Une seule instance d’une classe.
Prototype
- Clone,
- Héritage de prototype
Builder
- Construire un objet avec plusieurs éléments choisi (recette de cuisine).
- Permet de construire un objet complexe étape par étape.
- Le constructeur est appelé à chaque étape de la construction de l’objet.
- Le client n’a pas besoin de connaître les étapes de construction de l’objet, il suffit de lui fournir un objet de construction. Le pattern Builder est utilisé pour construire des objets complexes étape par étape. Il permet de séparer la construction d’un objet complexe de sa représentation finale, de sorte que le même processus de construction puisse créer différentes représentations.
Factory
Fabrique des objets en fonction d’un paramètre.
Abstract factory
Fabrique des objets en fonction d’un paramètre, mais avec plusieurs usines, Par exemple pour implémenter une charte graphique : il existe une fabrique qui retourne des objets (boutons, menus) dans le style de Windows, une qui retourne des objets dans le style de Motif, et une dans le style de Macintosh. Une fabrique abstraite est obtenue en utilisant une fabrique simple.
Structural patterns
Les patterns structuraux, qui structurent l’organisation des classes entre elles, comment elles sont liées.
Adapter
Adapte une interface à une autre:
- On veut utiliser une classe existante, mais dont l’interface ne coïncide pas avec celle escompté.
- On souhaite créer une classe réutilisable qui collabore avec des classes sans relations avec elle et encore inconnues, c’est-à-dire avec des classes qui n’auront pas nécessairement des interfaces compatibles.
- On a besoin d’utiliser plusieurs sous-classes existantes, mais l’adaptation de leur interface par dérivation de chacune d’entre elles est impraticable. Un adaptateur peut adapter l’interface de sa classe parente.
@startuml
abstract Cible{
+ requete()
}
Adaptateur : + requete()
Adapté : + requeteSpécifique()
note "requeteSpécifique()" as N1
Client -> Cible
Cible <|-- Adaptateur
Adapté <-- Adaptateur
Adaptateur . N1
@enduml
Composite
Permet d’agencer les objets dans des arborescences afin de pouvoir traiter de la même et unique façon les objets individuels et les combinaisons de ceux-ci. un objet contient d’autres objets :
- Un système de fichier
- Des objets graphiques (conteneurs et éléments graphiques élémentaire)
- DOM
@startuml
skinparam groupInheritance 2
class Client {
+ main()
}
abstract Composant {
+ opération()
+ ajouter(c: Composant)
+ supprimer(c: Composant)
+ getEnfant(i: int)
}
class Feuille{
+ opération()
}
class Composite{
+ opération()
+ ajoute(c: Composant)
+ supprimer(c Composant)
+ getEnfant(i; Int)
}
note "pour tout g parmi enfant g.opération();" as N
Client -> Composant
Composant <|-- Feuille
Composant <|-- Composite
Composant "1..*"<-- Composite
Composite .. N
@enduml
Decorator
Un objet contient un autre objet, et ajoute des fonctionnalités surchargeant les méthodes. Permet d’ajouter des fonctionnalités à un objet de manière dynamique.
@startuml
abstract Composant{
+ opération()
}
ComposantConcret : + opération()
abstract Décorateur{
+ opération()
}
class DécorateurConcretA{
- étatAjouté : int
+ oprétion()
}
class DécorateurConcretB{
+ opération()
+ comportementAjouté()
}
note right of Décorateur : composant.opération()
note right of DécorateurConcretB : Décoration.opération();\ncomportementAjouté();
Composant <|- ComposantConcret
Composant <|-- Décorateur
Composant <-- Décorateur
Décorateur <|-- DécorateurConcretA
Décorateur <|-- DécorateurConcretB
@enduml
Facade
Permet de fournir une interface unifiée pour un ensemble d’interfaces dans un sous-système.
Behavioral patterns
Les patterns comportementaux, qui définissent la communication entre objets.
Strategy
Permet de définir une famille d’algorithmes, encapsuler chacun d’eux et les rendre interchangeables. En général, les clients créent un objet StrategieConcrete, et le passent au contexte, par la suite les clients interagissent exclusivement avec le contexte.
@startuml
class contexte{
+ interfaceContexte()
}
Abstract Stratégie{
+ interfaceAlgorithme()
}
StratégieConcrèteA : + interfaceAlgorithme()
StratégieConcrèteB : + interfaceAlgorithme()
StratégieConcrèteC : + interfaceAlgorithme()
contexte -> Stratégie
Stratégie <|-- StratégieConcrèteA
Stratégie <|-- StratégieConcrèteB
Stratégie <|-- StratégieConcrèteC
@enduml
Command
Un objet encapsule une commande, et peut l’exécuter, encapsuler une demande sous la forme d’un objet, ce qui permet de paramétrer les clients avec différentes demandes.
@startuml
abstract Commande {
+ execute()
}
class CommandeConcrete{
+ execute()
}
class Recepteur {
+ action()
}
note "Recepteur.action()" as N1
Invocateur -> Commande
Commande <|-- CommandeConcrete
Client .> CommandeConcrete
Client -> Recepteur
CommandeConcrete --> Recepteur
CommandeConcrete .. N1
@enduml
Iterator
Permets de parcourir les éléments d’une collection sans révéler sa représentation interne (liste, pile, arbre, etc.).
Même appel pour n’importe quel type de collection, en java on utilise l’interface Iterable et l’interface Iterator
java.util.Iterator
et java.lang.Iterable
@startuml
class Client {
+ main()
}
interface Iterateur{
+ suivant()
+ existeSuivant()
+ supprimer()
}
abstract Agregat{
{abstract} + créeItérateur()
}
class AgregatContret{
+ créeItérateur()
}
Agregat <-Client
Client -> Iterateur
Agregat <|-- AgregatContret
Iterateur <|-- ItérateurConcret
AgregatContret -> ItérateurConcret
note "return new ItérateurConcret(this)" as N1
AgregatContret .. N1
@enduml
Observer
Permet à un objet de surveiller l’état d’un autre objet et d’être informé lorsque cet état change, Le design pattern Observer est utilisé pour établir une relation de type “un-à-plusieurs” entre des objets, où un objet principal, appelé “sujet” (ou “observable”), informe plusieurs autres objets, appelés “observateurs”, lorsqu’il subit un changement d’état. L’idée est de définir une relation de dépendance entre ces objets sans les coupler de manière rigide. Cela permet de mettre à jour automatiquement les observateurs lorsque le sujet change d’état, sans avoir à les relier explicitement.
@startuml
abstract Sujet{
+ attache(o : Observateur)
+ détache(o : Observateur)
+ notifie()
}
abstract Observateur{
+ miseAJour()
}
ObservateurConcret : + miseAJour()
note "Pour tout o dans Observateurs\n o.miseAJour()" as N
Sujet -> Observateur
Sujet <|-- SujetConcret
Observateur <|-- ObservateurConcret
Sujet .. N
@enduml
Mediator
Un objet centralise la communication entre plusieurs, objets, associe un objet à un autre (centrale d’avion).
State
Un objet change de comportement en fonction de son état (ex: un bouton qui change de couleur en fonction de son état). Modélise des objets dont le comportement varie en fonction de leur état interne. L’idée est de définir une classe abstraite représentant l’état général d’un objet, et des sous-classes concrètes représentant les différents états spécifiques. Chaque état peut définir ses propres comportements pour les différentes méthodes de l’objet. L’objet principal, appelé “contexte”, maintient une référence à un objet d’état particulier, qui détermine le comportement actuel de l’objet. En modifiant l’état du contexte, on peut modifier le comportement de l’objet.
@startuml
Contexte : + requette()
abstract État{
+ gerer(): void
}
ÉtatAConcret : + gerer()
ÉtatBConcret : + gerer()
note bottom of Contexte : etat.gerer()
Contexte -> État
État <|-- ÉtatAConcret
État <|-- ÉtatBConcret
@enduml
Visitor
Utilisez le pattern Visiteur quand vous voulez ajouter des capacités à un ensemble composite d’objets et que l’encapsulation n’est pas importante. Le Visiteur doit parcourir chaque élément du Composite : cette fonctionnalité se trouve dans un objet Navigateur. Le Visiteur est guidé par le Navigateur et recueille l’état de tous les objets du Composite. Une fois l’état recueilli, le Client peut demander au Visiteur d’exécuter différentes opérations sur celui-ci. Quand une nouvelle fonctionnalité est requise, seul le Visiteur doit être modifié.
- Permet d’ajouter des opérations à la structure d’un Composite sans modifier la structure elle-même.
- L’encapsulation des classes du Composite est brisée.
- Comme une fonction de navigation est impliquée, les modifications de la structure du Composite sont plus difficiles.
@startuml
abstract Visiteur{
+ visiteurElementconcretA(elemConcretA)
+ visiteurElementconcretB(elemConcretB)
}
class VisiteurConcrèt1{
+ visiteurElementconcretA(elemConcretA)
+ visiteurElementconcretB(elemConcretB)
}
class VisiteurConcrèt2{
+ visiteurElementconcretA(elemConcretA)
+ visiteurElementconcretB(elemConcretB)
}
abstract Élement{
+ accept(v: Visiteur)
}
class ÉlementConcrètA{
+ accept(v: Visiteur)
+ opérationA()
}
class ÉlementConcrètB{
+ accept(v: Visiteur)
+ opérationB()
}
note "v.visiteElelementConcretA()" as N
note "v.visiteElelementConcretB()" as N2
Client -> Visiteur
Visiteur <|-- VisiteurConcrèt1
Visiteur <|-- VisiteurConcrèt2
Client ---> StuctureDObjet
StuctureDObjet -> Élement
Élement <|-- ÉlementConcrètA
Élement <|-- ÉlementConcrètB
ÉlementConcrètA . N
ÉlementConcrètB . N2
@enduml
Les inconvénients des patterns
- Ils occasionnent plus de classes
- Ils peuvent être peu adaptés dans des environnements dit “limités” (comme Android)
- c’est à encapsuler ce qui varie.
- Ce principe nous enseigne à limiter chaque classe à une seule responsabilité. (Cohésion)
- Nous savons que nous devons éviter comme la peste de changer quelque chose à une classe (Couplage)
Autres patrons de conception
Object Pool
Ce patron permet d’économiser les temps d’instanciation et de suppression lorsque de nombreux objets ont une courte durée d’utilisation. Il consiste à administrer une collection d’objets qui peuvent être recyclés. Une méthode du Pool délivre un objet soit par une nouvelle instanciation, soit par recyclage d’un objet périmé. Lorsque les objets arrivent à la fin de leur cycle de vie, ils sont remis à la disposition du Pool pour un futur recyclage. Dans la phase d’instanciation, le Pool peut instancier plus d’un objet à la fois si l’algorithme d’instanciation a une complexité meilleure que O(n). Le patron Object Pool est particulièrement utile lorsque le nombre total de cycles de vie est très grand devant le nombre d’instances à un moment précis et que les opérations d’instanciation et/ou suppression sont coûteuses en temps d’exécution par rapport à leur recyclage.
Modèle-vue-contrôleur
Combinaison des patrons observateur, stratégie et composite, ce qui forme ainsi un patron d’architecture.
Inversion de contrôle
Injection de dépendances
Les principes de conception Orientée Objet
Principe Ouvert-Fermé
Les classes doivent être ouvertes à l’extension mais fermées au changement.
- Ouvert à l’ajout de code
- Fermé aux modifications du code existant
- Ne pas modifier le code qui fonctionne, ce qui est stable doit rester stable.
Principe d’inversion des dépendances
Dépendez des abstractions et non des classes concrètes.
- Les modules de haut niveau ne doivent pas dépendre des classes concrètes d’un module de bas niveau
- Les deux doivent dépendre d’abstraction
Principe d’Hollywood
“Ne nous appelez pas, nous vous appelerons.”
Les composants de bas niveau doivent s’adapter à un système, mais ce sont les composants de haut niveau qui déterminent quand appeler les composants de bas niveau.
Hollywood correspond aux composants de haut niveau, les acteurs correspondent aux composants de bas niveau. C’est Hollywood qui appelle les acteurs.
Autres principes en vrac
- Programmez des interfaces
- Encapsulez ce qui varie
- Couplez faiblement vos objets
- Préférez la composition à l’héritage (“à un” plutôt que “est un”)
- Une classe = Une responsabilité
- Qui est responsable de ?
source
- wikipedia
- headfirst : design patterns
- mes cours
- Fiche de revision de Théo