14/05/2020
Ecole Mohammadia d’Ingénieurs
Patrons de conception
Pr : Adil ANWAR
[email protected]
Année : 2019/2020
Plan
Principes de base de la conception objet : GRASP
Principes S.O.L.I.D : écrire du bon code
Principe de responsabilité unique
Principe d’ouverture/fermeture
Principe de substitution de Liskov
Principe de séparation d’interfaces
Principe d’inversion de dépendances
Design Patterns (GOF)
Les principes S.O.L.I.D
écrire du bon code !
Single responsibility principle (SRP)
Open-closed principle (OCP)
Liskov substitution principle (LSP)
Interface segregation principle (ISP)
Dependency inversion principle (DIP)
1
14/05/2020
Principe de la responsabilité unique (SRP)
Chaque classe dans votre application ne doit avoir qu’une seule
raison de changer (Robert Martin).
Une responsabilité = une raison de changer
Séparer les responsabilités couplées en classes distinctes
Chaque classe ou module doit se concentrer sur une seule tâche à la
fois.
tout ce qui est définit sur cette classe devrait être lié à cette unique
tâche
En appliquant le principe SRP, les classes deviennent plus petites et le
code est plus propre.
1. Robert C. Martin.
Principe de la responsabilité unique (SRP)
Considérons l’exemple simple d’une interface IUser qui
déclare 4 méthodes utilisés dans le processus d’inscription
et d’authentification des utilisateurs :
login : une méthode qui permet à un utilisateur de se connecter avec
un nom d’utilisateur et un mot de passe.
register : une méthode qui créer un nouveau utilisateur.
logError : méthode de journalisation des erreurs qui affiche dans la
console les erreurs de l’application.
sendEmail: méthode qui implémente la fonctionnalité d’envoi de
mails aux utilisateurs
Principe de la responsabilité unique (SRP)
Analysons l’interface IUser :
Nous avons regroupé dans cette interface 4 fonctionnalités plus ou
moins indépendantes
Supposons qu’il y’a un changement dans l’exigence de journalisation
des erreurs, et on veut ajouter la journalisation dans un fichier
d’érreurs au lieu de l’afficher dans la console changement dans la
classe et recompilation des classes dépendantes mêmes celles qui
ne sont pas concernées par la journalisation
Supposons qu’il y’a un changement dans la logique d’envoi de mail,
par exemple utiliser une API basée sur https ou lieu du protocole
SMTP.
À chaque fois on sera obligé de recompiler toute la classe et celles
qui ont dépendent regroupement de plusieurs responsabilités
distinctes dans une seule classe - une violation de SRP
6
2
14/05/2020
Principe de la responsabilité unique (SRP)
Solution :
Séparer l’interface IUser en plusieurs interfaces qui respectent le
principe de la responsabilité unique
1. Responsabilité d’inscription et d’authentification
2. Responsabilité de journalisation
3. Responsabilité d’envoie de mail
Principe de la responsabilité unique (SRP)
Exemple
Nous avons développé une application de gestion des employés et
nous devons trier les employés par nom, age ou salaire.
Solution :
On peut donc créer une classe employee qui implémente l’interface
Comparator.
class Employe implements Comparator {
int compare(Object o) { … } }
Principe de la responsabilité unique (SRP)
Discussions:
Employé est une entité métier, on ne connait pas dans
quel ordre elle doit être triée puisque l'ordre de tri est
imposé par une classe client.
Pire: chaque fois que les employés doivent être triés
différemment, nous devons recompiler la classe
Employé et tous ses classes clients.
Cause des problèmes: nous avons regroupés deux
responsabilités distinctes (c.-à-d. L'employé en tant
qu'entité métier avec la fonctionnalité de trie) dans une
seule classe - une violation de SRP
3
14/05/2020
Principe de la responsabilité unique (SRP)
<<interface>>
Comparator
int compare()
Register
Add(Department d, Employee e);
Employee
name
age
salary
Problème int compare()
Lorsqu'une nouvelle exigence de trier
les employés dans un ordre différent,
les classes Employee, Register et Client
getEmployees()
Client doivent tous être recompilés, { ;}
même si Register n'a rien à voir avec
le besoin de trier des employés.
It invokes
Collections.sort(aListofEmployees);
Principe d’ouverture / fermeture (OCP)
Vous devriez être capable d’étendre le comportement d’une classe sans
le modifier(Robert C. Martin)
Les entités logicielles doivent être ouvertes à l'extension
le code est extensible pour proposer des comportements qui n’étaient pas
prévus lors de sa création.
mais fermées aux modifications
Le code a été écrit et testé, on n’y touche pas pour éviter les régressions.
Les extensions sont introduites sans modifier le code existant
Pour rendre une classe ouverte pour l'extension, fermée pour la
modification, coder avec des interfaces (ou des classes abstraites), plutôt
qu’avec des implémentations (classes concrètes).
Principe d’ouverture / fermeture (OCP)
L’abstraction comme moyen d’ouverture/fermeture
L’ouverture/fermeture se pratique en faisant reposer le code « fixe »
sur une abstraction du code amené à évoluer.
En d’autres termes, l’OCP consiste à séparer le stable du variable,
mais il faut savoir identifier ce qui sera stable et variable pour
pouvoir les séparer.
Parmi les mécanismes principaux qui permettent de mettre en place
l’abstraction préconisée par l’OCP.
L’utilisation des classes d’interface (classes abstraites en Java, C++,
interfaces en Java )
4
14/05/2020
Principe d’ouverture / fermeture (OCP)
Exemple d’introduction
Utilisation de la « délégation abstraite »
A gère les classes c1 et c2.
Si un nouveau cas c3 doit être géré,
il faut modifier le code de A
en conséquence
(opérations A.foo() et A.bar()) :
Principe d’ouverture / fermeture (OCP)
Exemple d’introduction
Le code de A peut être ouvert aux extensions et fermé aux
modifications en introduisant une interface I dont dérivent des classes
C1 et C2 et aussi C3:
Puisque A repose uniquement sur l'interface I, il devient possible
d'ajouter un nouveau cas c3 sous la forme d'une classe C3 dérivée de I,
sans avoir à modifier A.
<<interface>>
A I
foo() foo()
bar() bar()
void foo () {
i.foo();
} void bar () { C1 C2 C3
i.bar();
}
Principe d’ouverture / fermeture (OCP)
Exemple :
Comment faire en sorte que la voiture aille plus vite à l’aide d’un turbo?
Il faut changer la voiture avec la conception actuelle…
15
5
14/05/2020
Principe d’ouverture / fermeture (OCP)
Une classe ne doit pas dépendre d’une classe Concrète
Elle doit dépendre d’une classe abstraite
...et utiliser le polymorphisme
Principe d’ouverture / fermeture (OCP)
Employee
+int EmpType
Faculty Staff Secretary Engineer
+getOffice() +getDept() +getTypeSpeed() +getEngTYpe()
void printEmpRoster(Employee[] emps) {
Un exemple de Et si nous
for (int i; i<emps.size(); i++) {
Ce qu'il ne faut pas devons
if (emps[i].empType == FACULTY)
faire! ajouter un
Qu'est-ce qui ne va printfFaculty((Faculty)emps[i]); ingénieur ??
pas avec ce code? else if (emps[i].empType ==STAFF)
printStaff((Staff)emps[i]);
else if (emps[i].empType == SECRETARY)
printSecretary((Secretary)emps[i]);
}
}
Principe d’ouverture / fermeture (OCP)
Employee
+printInfo()
Faculty Staff Secretary Engineer
+printInfo() +printInfo() +printInfo +printInfo()
void printEmpRoster(Employee[] emps) {
for (int i; i<emps.size(); i++) {
emps[i].printInfo();
}
}
Lorsque la classe Engineer est ajoutée, la méthode
printEmpRoster () n'a même pas besoin de recompiler.
PrintEmpRoster () est ouverte à l'extension, fermée à
la modification.
6
14/05/2020
Principe d’ouverture/fermeture(OCP) : allons plus loin (1)
Le but de cette méthode est de calculer le prix total d’un ensemble de
produits «Product»
public double totalPrice(Product[] products) {
double total = 0.0;
for (int i=0; i< products.length; i++) {
total += products[i].getPrice();
}
return total;
}
Principe d’ouverture/fermeture(OCP) : allons plus loin (2)
«Mais le département comptabilité décrète que les pièces de la carte
mère et les pièces de mémoire devraient avoir une prime appliquée lors de
la détermination du prix total.»
Que pensez-vous du code suivant?
public double totalPrice(Product[] products) {
double total = 0.0;
for (int i=0; i< products.length; i++) {
if (products[i] instanceof Motherboard)
total += (1.45 * products[i].getPrice());
else if (products[i] instanceof Memory)
total += (1.27 * products[i].getPrice());
else
total += products[i].getPrice();
}
return total;
}
Principe d’ouverture/fermeture(OCP) : allons plus loin (3)
Voici des exemples de classes Product et ConcreteProduct
// Class Product is the superclass for all parts.
public class Product {
protected double price;
public Product(double price) (this.price = price;}
public void setPrice(double price) {this.price = price;}
public double getPrice() {return price;}
}
// Class ConcreteProduct implements a product for sale.
// Pricing policy explicit here!
public class ConcreteProduct extends Product { But now we must modify each
public double getPrice() { subclass of Part whenever the pricing
policy changes!
// return (1.45 * price); //Premium
return (0.90 * price); //Labor Day Sale
}
}
7
14/05/2020
Principe d’ouverture/fermeture(OCP) : allons plus loin(4)
Une meilleure idée est d'avoir une classe PricePolicy qui peut être utilisée
pour fournir différentes stratégies de tarification :
Principe de conception :
Protection des variations : Identifier les points de variation et
d’évolution, et séparer ces aspects de ceux qui demeurent constants.
// The Product class now has a contained PricePolicy object.
public class Product {
private double price;
private PricePolicy pricePolicy;
public void setPricePolicy(PricePolicy pricePolicy) {
this.pricePolicy = pricePolicy;}
public void setPrice(double price) {this.price = price;}
public double getPrice() {return pricePolicy.getPrice(price);}
}
Principe d’ouverture/fermeture(OCP) : allons plus loin (5)
/**
* Class PricePolicy implements a given price policy.
*/
public class PricePolicy {
private double factor;
public PricePolicy (double factor) {
this.factor = factor;
}
public double getPrice(double price) {return price * factor;}
}
D’autres politiques comme un calcul de la ristourne par «seuils» est
maintenant possible ...
Avec cette solution, nous pouvons définir dynamiquement des stratégies de
tarification au moment de l'exécution en modifiant l'objet PricePolicy auquel
un objet Product existant fait référence.
Bien sûr, dans une application réelle, le prix d'un produit et sa politique de prix
associée peuvent être contenus dans une base de données.
Principe d’ouverture/fermeture(OCP): conclusion
Identifier les points d’ouverture/fermeture en fonction des
Besoins d’évolutivité exprimés par le client
Besoins de flexibilité pressentis par les développeurs
Changements répétés constatés au cours du développement
Il n'est pas possible que tous les modules d'un système logiciel satisfirent
l'OCP, mais nous devons essayer de minimiser le nombre de modules qui ne
le satisfont pas.
Le principe ouvert-fermé est vraiment le cœur de la conception OO.
La conformité à ce principe donne le plus haut niveau de réutilisabilité et de
maintenabilité.
8
14/05/2020
Principe de séparation des interfaces (ISP)
De nombreuses interfaces spécifiques au client valent mieux qu'une
interface à usage général. (Robert C. Martin).
«Les clients ne devraient pas être obligés de dépendre de méthodes qu'ils
n'utilisent pas.» - R. Martin
Lorsque nous regroupons des fonctions pour différents clients en une
seule interface / classe, nous créons un couplage inutile entre les clients.
Lorsqu'une classe client provoque le changement d'interface, tous les
autres classes clients sont obligés de se recompiler.
Un client doit avoir des interfaces avec uniquement ce dont il a besoin
Incite à avoir des interfaces petites pour ne pas forcer des classes à
implémenter les méthodes qu’elles ne veulent pas.
Principe de séparation des interfaces (ISP)
Les clients ne doivent pas être forcés de dépendre d'interfaces qu'ils
n'utilisent pas
Chaque client ne doit «voir» que les services qu'il utilise réellement
Evite une tentation courante : accumuler dans une classe un ensemble
de services sous prétexte que la classe contient les informations
nécessaires
Solution : utiliser des interfaces différentes par type de client différent
Principe de séparation des interfaces (ISP)
Chaque client voit une interface
trop riche dont une partie ne Pollution
l'intéresse pas
Chaque client peut être impacté
par des changements d'une
interface qu'il n'utilise pas
Solution
27
9
14/05/2020
Principe de séparation des interfaces (ISP)
Mise en œuvre
Par héritage multiple (qd
permis)
Par classe d'adaptation
28
Principe de séparation des interfaces (ISP)
Exemple d’illustration
<<interface>>
IPrintTasks
+printContent(String content)
+scanContent(String content)
…….
HPLazerJetPrinter
Principe de séparation des interfaces (ISP)
Exemple d’illustration
10
14/05/2020
Principe de séparation des interfaces (ISP)
Exemple d’illustration
Problème 1 : Supposons que nous avons une deuxième classe
EPsonPrinter qui souhaite implémenter l’interface IPrintTasks mails
elle ne dispose pas des fonctionnalités de fax ou de photocopie.
31
Principe de séparation des interfaces (ISP)
Exemple d’illustration
Problème 2 : Supposons que nous souhaitons ajouter une méthode
printDuplexContent dans l’interface pour supporter une nouvelle
fonctionnalité d’impression en mode recto verso.
32
Principe de séparation des interfaces (ISP)
Exemple d’illustration
Pour résoudre ce problème, il suffit d’appliquer le principe ISP qui
consiste à faire un refactoring de l’interface IPrintTasks en trois
interfaces plus petites :
Une interface pour les fonctionnalités de base
Une interface pour la fonction se scan
Une interface pour la fonction d’impression en recto verso
33
11
14/05/2020
Principe de substitution de Liskov (LSP)
Les sous-types doivent être substituables à leurs types de
base.(Robert C. Martin).
(Turing Price, 2008)
Principe de substitution de Liskov (LSP)
Définition : Si une propriété P est vraie pour une instance x d'un type T,
alors cette propriété P doit rester vraie pour tout instance y d'un sous-type
de T »
Implications :
Le «contrat» défini par la classe de base (pour chacune de ses
méthodes) doit être respecté par les classe dérivées
L'appelant n'a pas à connaître le type exact de la classe qu'il manipule :
n'importe quelle classe dérivée peut être substituée à la classe qu'il
utilise
→ Principe de base du polymorphisme :
Si on substitue une classe par une autre dérivée de la même
hiérarchie: comportement (bien sûr) différent mais conforme
Principe de substitution de Liskov (LSP)
Les sous-types doivent être substituables à leurs types de
base.
N’exigez pas plus, ne promettez rien de moins
n'exigez pas plus: la sous-classe accepterait tous les arguments que la
superclasse accepterait.
ne promettez rien de moins: toute hypothèse valable lorsque la
superclasse est utilisée doit l'être lorsque la sous-classe est utilisée.
Héritage d'interface - Le LSP doit être conforme.
Héritage d'implémentation - utilisez la composition au lieu de
l'héritage (en Java).
12
14/05/2020
Principe de substitution de Liskov (LSP)
Exemple (1/4):
Supposons la classe de base Employe qui proposent deux méthodes :
Principe de substitution de Liskov (LSP)
Exemple (2/4):
Les classes dérivées : PermantentEmploye, TomporaryEmploye qui
proposent des implémentations des méthodes calculateBonus et
getBaseSalary
Principe de substitution de Liskov (LSP)
Exemple (3/4):
13
14/05/2020
Principe de substitution de Liskov (LSP)
Exemple (4/4)
Supposons qu’on a une classe ContractEmploye dérivé de Employe mais qui ne
proposent pas d’implémentation de la méthode calculateBonus //règle métier
Principe de l’inversion de dépendance
Les abstractions ne doivent pas dépendre de détails. Les
détails doivent dépendre d'abstraction. R. C. Martin
Réduire les dépendances sur les classes concrètes
Program to interface, not implementation »
Ne dépendre QUE des abstractions, y compris pour les classes
de bas niveau
Permet OCP (concept) quand l’inversion de dépendance c’est
la technique!
Principe de l’inversion de dépendance
Technique
Supposons que les classes A et B son reliées par une association dirigée
du A vers B, on dit que A dépend de B.
14
14/05/2020
Principe de l’inversion de dépendance
Client Layer High-level modules make calls to low-
level modules.
Business Layer
Utility Layer
The upper-level layer is dependent upon
lower-level layers.
Principe de l’inversion de dépendance
Client Dependency Inversion: Lower-level layers is
dependent upon upper-level layers.
Client
Layer Business
Business
<<interface>> Layer
Business
Interface Utility
<<interface>>
Utility Utility
Interface Layer
Ownership Inversion: The client (upper-level layer) owns
the interface, not the lower-level layers
Principe de l’inversion de dépendance
Exemple
45
15
14/05/2020
Principe de l’inversion de dépendance
Exemple (1/4): Classe client
46
Principe de l’inversion de dépendance
Exemple (2/4) : classe Compte
47
Principe de l’inversion de dépendance
Exemple (3/4) : Les classes utilitaires : Logger et EmailSender
48
16
14/05/2020
Principe de l’inversion de dépendance
Exemple (4/4) : la classe Client de test
49
Résumé des principes SOLID
Single-responsibility principle
There is only one source that may the class to change
Open-closed principle
Open to extension, closed for modification
Liskov substitution principle
A subclass must substitutable for its base class
Interface segregation principle
A client should not be forced to depend on methods it does not use.
Dependency inversion principle
Low-level (implementation, utility) classes should be dependent on
high-level (conceptual, policy) classes
17