Design Principles
Design Principles
Design Patterns
Design Principles
I.mouakher
Introduction
A good software design organizes the code in a way that it is "easy to
understand, change, maintain and reuse.“
Values of a Good Design - Communication, Simplicity, Flexibility
Design principles are (often opinionated) guidelines derived from experience
of programmers about software design that usually take the form of do's and
don'ts.
The Design principles are the Commandments of OO Programming.
2
COUPLAGE & COHÉSION
3
Couplage & Cohésion
Nous allons explorer deux principes fondamentaux
sur la cohésion et le couplage. Le chemin vers un logiciel orienté objet
modifiable et maintenable commence par des classes hautement cohérentes
et faiblement couplées.
Deux critères absolus :
1) Cohésion
2) Couplage
4
Couplage
Mesure la force d'interaction entre les modules.
Le degré de dépendance de chaque module aux autres modules.
Il faut minimiser le couplage dans une conception.
Questions associées :
Comment les modules collaborent ensemble ?
Qu'ont-ils besoin de connaître les uns des autres?
Indice
La liste des importations est un bon indicateur de la force de couplage: e.g, nombre
d'inclusions (#include en C++, C#), import (Java, Python).
5
Couplage
Couplage fort
Quand une classe A est lié à une classe B, on dit que la classe A est fortement couplée à la classe B.
La classe A ne peut fonctionner qu’en présence de la classe B. Si une nouvelle version de la classe B
(soit B2), est crée, on est obligé de modifier dans la classe A
Modifier une classe implique:
Il faut disposer du code source
Il faut recompiler, déployer et distribuer la nouvelle application aux clients
Ce qui engendre un cauchemar au niveau de la maintenance de l’application
Couplage faible
Considérons une classe A qui implémente une interface IA, et une classe B qui implémente une
interface IB. Si la classe A est liée à l’interface IB par une association, on di que la classe et la classe B
sont liées par un couplage faible. La classe B peut fonctionner avec n’importe quelle classe qui
implémente l’interface IA.
Avec le couplage faible, on peut créer des applications fermée à la modification et ouvertes à
l’extension
Faible couplage
Le faible couplage favorise :
la faible dépendance entre les classes,
la réduction de l'impact des changements dans une classe,
la réutilisation des classes ou modules.
Pour affaiblir le couplage, il faut :
diminuer la quantité de paramètres passés entre les modules,
éviter d'utiliser des variables globales (par exemple, si une mauvaise valeur est
assignée, détecter la fonction/classe incorrecte est plus difficile), il vaut mieux
passer les valeurs en paramètres.
Forte cohésion
La cohésion mesure la compréhensibilité des classes. Une classe doit avoir des
responsabilités cohérentes, et ne doit pas avoir des responsabilités trop variées. Une classe
ayant des responsabilités non cohérentes est difficile à comprendre et à maintenir.
La forte cohésion favorise :
la compréhension de la classe,
la maintenance de la classe,
la réutilisation des classes ou modules.
Sinon, la faible cohésion est une situation dans laquelle un élément donné a
trop d'activités sans rapport avec ses responsabilités. Les éléments avec une
faible cohésion souffrent souvent d'être difficile à comprendre, difficile à
réutiliser, difficile à maintenir et réfractaire au changement
SOLID PRINCIPES
9
SOLID
Bien qu'ils s'appliquent à toute conception orientée objet, les principes SOLID peuvent
également former une philosophie de base pour des méthodologies telles que le
développement agile ou le développement de logiciels adaptatifs.
La théorie des principes SOLID a été introduite par Martin dans son article Design Principles
and Design Patterns de 2000.
10
SOLID
Introduit par Robert C. Martin.
En programmation orientée objet, SOLID est un acronyme mnémonique qui regroupe cinq
principes de conception
Single responsibility principle (Responsabilité unique)
une classe, une fonction ou une méthode doit avoir une et une seule responsabilité
Open/closed principle (Ouvert/fermé )
une entité applicative (classe, fonction, module ...) doit être fermée à la modification directe mais ouverte à
l'extension
Liskov substitution principle (Substitution de Liskov)
une instance de type T doit pouvoir être remplacée par une instance de type G, tel que G sous-type de T, sans
que cela ne modifie la cohérence du programme
Interface segregation principle (Ségrégation des interfaces)
préférer plusieurs interfaces spécifiques pour chaque client plutôt qu'une seule interface générale
Dependency inversion principle (Inversion des dépendances)
il faut dépendre des abstractions, pas des implémentations
12
Responsabilité unique (SRP: Single Responsibility
Principle)
Le principe de responsabilité unique, réduit à sa plus simple
expression, est qu'une classe donnée ne doit avoir qu'une seule
responsabilité, et, par conséquent, qu'elle ne doit avoir qu'une
seule raison de changer.
14
Exemple
public class Customer
{ public int Id;
public string Name;
public bool Active;
public void ActivateCustomer() { Active = true; }
15
Exemple
public class Customer
{ public int Id;
public string Name;
public bool Active;
public void ActivateCustomer() { Active = true; }
16
How does this principle help us to build better
software?
Let's see a few of its benefits:
Testing – A class with one responsibility will have far fewer test
cases.
Lower coupling – Less functionality in a single class will have
fewer dependencies.
Organization – Smaller, well-organized classes are easier to
search than monolithic ones.
Ouvert/fermé (OCP: Open/closed Principle)
Définition: Les modules qui se conforment au principe
ouvert/ferme ont deux attributs principaux.
Ils sont "ouverts pour l'extension". Cela signifie que le comportement du
module peut être étendu, que l'on peut faire se comporter ce module de
façons nouvelles et différentes si les exigences de l'application sont
modifiées, ou pour remplir les besoins d'une autre application.
Ils sont "Fermés à la modification". Le code source d'un tel module ne
peut pas être modifié. Personne n'est autorisé à y apporter des
modifications. Bref, il ne faut pas briser la logique de l’héritage.
Ouvert/fermé (OCP: Open/closed Principle)
Ouvert à une extension - vous devez concevoir vos classes de manière à
ce que de nouvelles fonctionnalités puissent être ajoutées à mesure que
de nouvelles exigences sont générées.
Fermé pour modification - Une fois que vous avez développé une classe,
vous ne devez jamais la modifier, sauf pour corriger des bogues.
La conception et le code doivent être faits de manière à ce que de
nouvelles fonctionnalités soient ajoutées avec un minimum ou aucun
changement dans le code existant
Lorsqu'il est nécessaire d'étendre les fonctionnalités - évitez les
couplages étroits, n'utilisez pas la logique if-else/switch-case, effectuez la
refactorisation du code si nécessaire.
Exemple 1
public void ProcessPayment()
{ if (PaymentMethod == PaymentMethod.VisaCard)
{ // Some implementation heren}
else if (PaymentMethod == PaymentMethod.MasterCard)
{// Some implementation heren}
else if (PaymentMethod == PaymentMethod.Cash)
{ // Some implementation here }
// Some implementation here
}
=> OCP violé : le code est ouvert à la modification, si un nouveau mode de paiement est ajouté, la
classe doit être modifiée.
21
Exemple 1
public void ProcessPayment()
{ // Some implementation here
PaymentMethod.ProcessPayment();
// Some implementation here
}
22
Exemple 1
LSP violé
Exemple : LSP garanti
Refactored BankingAppWithdrawalService
Séparation des Interfaces (ISP: Interface
Segregation Principle)
« Tu veux que je mette ça où? »
De nombreuses interfaces spécifiques aux clients valent mieux
qu’une seule interface.
Séparation des Interfaces (ISP: Interface
Segregation Principle)
Définition: Les clients d'une entité logicielle ne doivent
pas avoir à dépendre d'une interface qu'ils n'utilisent pas.
Ce principe apporte principalement une diminution du
couplage entre les classes (les classes ne dépendant
plus les unes des autres).
On peut comprendre que l’ISP est lié avec le SRP par le
fait que l’interface peut définir plusieurs concepts qui ne
sont pas nécessairement liés.
Exemple
Dans l’exemple ci-dessous, la classe
Vehicle a deux comportements. Nous
pouvons démarrer la voiture ou allumer
les lumières.
En supposant qu’une deuxième classe
aurait la responsabilité d’allumer les
lumières comme suit:
Afin de respecter au maximum l’ISP, il
faut se demande pourquoi une classe
responsable d’allumer les lumières
devrait savoir qu’un véhicule peut être
démarrer. 37
Exemple
On peut alors extraire l’interface afin que notre classe ne
connaisse que ce dont elle a besoin:
38
Inversion des dépendances (DIP:
Dependency Inversion Principle)
« Souderiez-vous une lampe directement sur le câblage électrique dans le
mur? »
Définition: Les modules de haut niveau ne doivent pas dépendre des
modules de bas niveau. Les deux doivent dépendre d'abstractions. Les
abstractions ne doivent pas dépendre des détails. Les détails doivent
dépendre des abstractions.
DIP says that modules should depend upon interfaces or abstract classes,
not concrete classes. It's an inversion because implementations depend
upon abstractions and not the other way round. Instead of high-level modules
depending on low-level modules, let's decouple them and make use of
abstractions.
DIP
41
DIP
Le DIP ou principe d’inversion de dépendance nous dit
que les dépendances d’une classe ne devraient jamais
être concrètes. Puisqu’elle ne doit pas connaître
l’implémentation de ses dépendances, nous pouvons
nous assurer du respect de ce principe en implémentant
le mécanisme d’injection de dépendances
42
Exemple
Supposons que vous avez une classe responsable de la
journalisation. Cette classe est utilisée dans un service
afin de journaliser les entrées et sorties:
Ici, Logger et SomeService sont
fortement couplées. Le problème
surgira lorsqu’il faudra journaliser
dans un fichier au lieu de la console,
surtout si Logger est utilisée comme
telle partout dans l’application. 43
Exemple
Par contre, si nous dépendons d’une interface:
44
Exemple
Et voilà! SomeService n’a plus connaissance de la
technologie utilisée pour la journalisation.
45
Autres Principes
KISS ( keep it simple, stupid)
DRY (don’t repeat yourself)
YAGNI (you ain't gonna need it)
….
46
QCM
47
Question 1
Un nouveau type d'indemnités de congé doit être ajouté dans
un système logiciel pour les ressources humaines. Le code
d'origine doit être considérablement modifié pour supporter
la fonctionnalité. Quel est le principe de conception SOLID
enfreint dans cette situation ?
Ouvert/fermé
Inversion des dépendances
Substitution de Liskov
Responsabilité unique
Ségrégation des interfaces
48
Réponse
Ouvert/fermé
Dans ce cas, vous n'étendez pas la classe, mais vous modifiez
le code d'origine. Selon le principe ouvert/fermé, une classe
devrait être ouverte pour l'extension, mais fermée à la
modification. Vous enfreignez ce principe ici.
49
Question 2
Une classe dérivée implémente une méthode
redéfinie en lançant une
UnsupportedOperationException. Quel principe de
conception SOLID est enfreint dans cette situation ?
Ouvert/fermé
Inversion des dépendances
Substitution de Liskov
Responsabilité unique
Ségrégation des interfaces
50
Réponse
Substitution de Liskov
Dans cette situation, vous êtes confronté à un problème
d'héritage. Votre implémentation de bas niveau ne se conforme
pas à celles de haut niveau. Il s'agit d'une violation de la
substitution de Liskov.
51
Question 3
Si une méthode d'une classe présente de trop
nombreux cas d’exécution possibles, elle est difficile
à tester. Quel principe de conception SOLID est
enfreint dans cette situation ?
Ouvert/fermé
Inversion des dépendances
Substitution de Liskov
Responsabilité unique
Ségrégation des interfaces
52
Réponse
Responsabilité unique
Si une méthode a de nombreuses options, cela signifie qu'elle
effectue plusieurs choses, et ainsi qu'elle a plusieurs
responsabilités. Cela enfreint le principe de responsabilité
unique.
53
Question 4
Quel est le principe SOLID enfreint par l'extrait de code
suivant ?
Ouvert/fermé
Inversion des dépendances
Substitution de Liskov
Responsabilité unique
Ségrégation des interfaces
54
Réponse
Ségrégation des interfaces
L'interface présente plusieurs responsabilités, ce qui enfreint le
principe de ségrégation des interfaces. Et puisque le principe
de responsabilité unique concerne les classes, cela ne peut
pas être la bonne réponse.
55
Question 5
Quel extrait de code complète le code suivant et lui permet de se conformer
au principe d'inversion des dépendances ?
56
Réponse
❌ Réponse 1 : l'élément Motorcycle a introduit un nouvel ensemble de
méthodes. Tout élément qui l'utilisera devra changer ses méthodes afin
d'appeler ces nouvelles fonctions. Ici, la classe de bas niveau pilote la classe
de haut niveau.
❌ Réponse 2 : l'élément SelfDrivingCar dispose des mêmes méthodes, mais
n'a pas inclus « implements Driveable », de sorte qu'il ne peut pas être
remplacé par un élément Driveable.
✅ Réponse 3 : une classe de haut niveau (Driver) utilise l'interface fournie
(Driveable).
❌ Réponse 4 : la classe Driver doit appeler des méthodes, pour chaque
véhicule, en fonction de la nature de ce dernier. Il n'utilise pas du tout
d'interface pour interagir. 57
Question 6
Lequel des extraits de code suivants complète correctement le principe
ouvert/fermé de l'extrait de code suivant ?
58
Réponse
❌ Réponse 1 : aucune des classes n'implémente l'interface
Rollable, dont a besoin la classe Game.
✅ Réponse 2
❌ Réponse 3 : la classe Game a été modifiée de façon à
seulement utiliser des objets Die. Elle n'est pas ouverte à la
modification si vous utilisez un mécanisme différent.
❌ Réponse 4 : une classe entièrement nouvelle, MyGame, a été
introduite pour gérer une implémentation différente. Elle n'est
ouverte à rien.
59
Exercice
Consider the following partial design of an application.