2. Définition
• SOLID est un acronyme qui désigne les 5 premiers principes de la
programmation orientée objet décrit par Robert Cecil Martin (Uncle
Bob)
• Il n’existe pas de hiérarchie dans ces principes
• Dans la pratique ces principes sont très imbriqués
2
3. Avant de commencer
• SOLID s’articule au cœur d’une architecture orienté objet,
• On peut considérer ces principes comme un « art de faire »,
de bonnes pratiques
• « Principe », pas pattern. Si SOLID va nous dire ce qu’il faudrait faire,
les patterns vont nous dire comment le faire
• Le pragmatisme doit rester maitre, une architecture trop complexe
peut en réalité nuire à ce pourquoi SOLID existe : la maintenabilité
3
4. 5 principes
• SRP : Single Responsibility Principle
Une classe doit avoir une et une seule responsabilité
• OCP : Open/Close Principle
Une entité doit être ouverte aux extensions et fermée aux modifications
• LSP : Liskov Substitution Principle
Les sous-types doivent être interchangeables par leurs types de base
• ISP : Interface Segregation Principle
Un client ne doit pas être forcé de dépendre de méthodes qu’il n’utilise pas
• DIP : Dependency Inversion Principle
Il faut dépendre des abstractions, pas des implémentations
4
6. • Importer des données d'un fichier CSV dans une base de données.
SRP : Single Responsibility Principle
• Quel est le problème avec cette conception ?
• Il y a 2 responsabilités donc 2 raisons de changer.
1. Lire le fichier CSV sous forme d'enregistrements.
2. Stocker les enregistrements dans la base de données.
Exemple
6
7. public class EmployeeService{
private String getname(){
//implementation
}
private double getIncome(){
//implementation
}
private Date getDateOfJoining(){
//implementation
}
public boolean isPromotionDueThisYear(){
//promotion logic implementation
}
public Double calcIncomeTaxForCurrentYear(){
//income tax logic implementation
}
}
La classe EmployeeService semble
logiquement correcte.
• Il récupère tous les attributs d'employé
tels que : nom, âge et dateOfJoining.
• Il vous indique même si l'employé est
éligible à une promotion cette année.
• Il calcule l'impôt sur le revenu qu'il doit
payer pour l'année.
SRP – Exemple
7
8. La logique de déterminer si l'employé est éligible à avoir une promotion cette
année n'est en réalité pas une responsabilité qui appartient à l'employé. Le
service RH de l’entreprise assume cette responsabilité en fonction des
politiques RH de l’entreprise qui peuvent changer chaque années.
Lors de tout changement de ces politiques RH, la classe EmployeeService devra
être mise à jour car elle est actuellement responsable de la détermination des
promotions.
SRP – Exemple
8
9. De même, le calcul de l'impôt sur le revenu n'est pas une responsabilité de
l'employé. Il est de la responsabilité du service financier de s’occuper de la
structure fiscale actuelle qui peut être mise à jour chaque année.
Si la classe d'employé service détient la responsabilité du calcul de l'impôt
sur le revenu, chaque fois que la structure / les calculs fiscaux changent, la
classe d'employé devra être modifiée.
Enfin, la classe des employés devrait avoir la responsabilité unique de
maintenir les attributs de base d'un employé.
SRP – Exemple
9
10. Nous pouvons déplacer la logique de détermination de promotion de la classe
Employee vers une autre classe HRPromotions :
public class HRPromotions{
public boolean isPromotionDueThisYear(Employee emp){
//promotion logic implementation using the employee information
passed
}
}
De même, déplaçons la logique de calcul de l'impôt sur le revenu de la classe
Employee vers la classe FinITCalculations -
public class FinITCalculations{
public Double calcIncomeTaxForCurrentYear(Employee emp){
//income tax logic implementation using the employee information passed
}
}
SRP – Exemple
10
11. SRP : Single Responsibility Principle
L’idée est que si une responsabilité évolue, alors on ne risque pas de
casser les autres.
• Le code devient plus compréhensible car découpé en
responsabilité
• facile à tester.
• plus facile à étendre.
11
12. La cohésion recommande de placer un ensemble des éléments (composants,
classes, méthodes) ayant des rôles similaires ou dédiés à une même problématique.
La cohésion d’une classe recommande de placer un ensemble d’attributs et de
méthodes ayant des rôles similaires ou dédiés à une même responsabilité.
Cohésion
Mesurer la cohésion
Soient Vi des variables et Mi des méthodes
12
13. LCOM (Lack of Cohesion in Methods)
Considérons une classe C avec les méthodes M1, M2… Mn.
Soit {Ii} = ensemble de variables d'instance utilisées par la méthode Mi.
Il existe n ensembles de ce type {I1},… {In}.
LCOM = le nombre d'ensembles disjoints formés par l'intersection des n
ensembles.
13
15. OCP : Open/Closed Principle
• Une classe doit être ouverte à l'extension, mais fermée à la modification
• Sauf pour la correction de bug, l’idée est de ne jamais modifier un code déjà
existant surtout s’il a déjà été testé et approuvé.
• Ce principe s’applique pour structurer notre code afin de nous procurer l'un des
plus grands avantages de la programmation orientée objet : la réutilisation et la
maintenabilité.
• Faire bon usage de l’abstraction et du polymorphisme afin d’étendre un
comportement sans le modifier (Decorator, Factory).
15
17. • La composition avec le pattern Strategy répond aussi à ce principe
OCP : Open/Close Principle
bon usage de l’abstraction
17
18. class Circle {
public void paint() {
System.out.println("A Circle");
}
}
class Square {
public void display() {
System.out.println("A Square");
}
}
OCP : Open/Close Principle
Exemple
18
19. class Drawing {
private List shapes;
public Drawing() {
shapes = new ArrayList();
}
protected boolean is_shape(Object s) {
return s instanceof Circle || s instanceof Square;
}
@SuppressWarnings("unchecked")
final public void add(Object s) {
if (is_shape())
shapes.add(s);
else
throw new IllegalArgumentException("Unknow Shape");
}
public void drawShape(Object s) {
if (s instanceof Circle)
((Circle) s).paint();
else if (s instanceof Square)
((Square) s).display();
}
final public void drawAllShapes() {
for (Object o : shapes)
drawShape(o);
}
} 19
20. public class DrawAllShapes {
static public void main(String... argv) {
Drawing myDrawing = new Drawing();
myDrawing.add(new Circle());
myDrawing.add(new Square());
myDrawing.add(new Circle());
myDrawing.add(new Square());
myDrawing.add(new Circle());
myDrawing.drawAllShapes();
}
}
20
21. public abstract class Shape {
public abstract void draw();
}
class Circle extends Shape {
public void draw() {
System.out.println("A Circle");
}
}
class Square extends Shape {
public void draw() {
System.out.println("A Square");
}
}
21
Code Refractoring
22. class Drawing {
private List<Shape> shapes;
public Drawing() {
shapes = new ArrayList<Shape>();
}
final public void add(Shape s) {
shapes.add(s);
}
final public void drawAllShapes() {
for (Shape s: shapes)
s.draw();
}
}
22
24. LSP : Liskov Substitution Principle
• Le principe LSP stipule que les objets d'une superclasse doivent être remplaçables
par des objets de ses sous-classes sans interrompre l'application.
• Les classes enfants ne doivent jamais briser les définitions de type de classe
parente
Si S est un sous-type du type T
Si φ(x) est une propriété démontrable pour tout objet x
de type T, alors φ(y) est vraie pour tout objet y de type S.
24
25. LSP : Liskov Substitution Principle
LSP
class Bird {
public void fly(){}
public void eat(){}
}
class Crow extends Bird {}
class Ostrich extends Bird{
fly(){
throw new UnsupportedOperationException();
}
}
public BirdTest{
public static void main(String[] args){
List<Bird> birdList = new ArrayList<Bird>();
birdList.add(new Bird());
birdList.add(new Crow());
birdList.add(new Ostrich());
letTheBirdsFly ( birdList );
}
static void letTheBirdsFly ( List<Bird> birdList ){
for ( Bird b : birdList ) {
b.fly();
}
}
}
Ne pas penser « tous les oiseaux volent sauf
l’autruche » mais plutôt
« tous les oiseaux ne volent pas, l’autruche en est un »
25
26. class Rectangle
{
protected int width;
protected int height;
public void setWidth(int width)
{ this.width = width }
public void setHeight(int height)
{ this.height = height; }
public int area()
{ return width * height; }
//getters
}
Un carré est un rectangle ?!!
LSP : Liskov Substitution Principle
26
28. class LiskovSubstitutionPrincipleViolated
{
public static void main(String args[])
{
Rectangle r = new Rectangle();
r.setWidth(5);
r.setHeight(10);
System.out.println(r.area());
}
}
LSP : Liskov Substitution Principle
new Square();
28
29. Pour obtenir une solution compatible LSP, nous créons des frères Rectangle et Square.
Nous introduisons l'interface Shape pour regrouper les méthodes courantes.
LSP : Liskov Substitution Principle
29
30. 30
Preconditions et Postconditions
Précondition : Une condition préalable d'une classe est une règle qui doit être en place
avant qu'une action puisse être entreprise.
Par exemple, avant d'appeler une méthode qui lit à partir d'une base de données, vous
devrez peut-être satisfaire la condition préalable que la connexion à la base de données
est ouverte.
Poscondition : Les postconditions décrivent l'état des objets après la fin d'un processus.
Par exemple, on peut supposer que la connexion à la base de données est fermée après
l'exécution d'une instruction SQL.
Le LSP stipule que les préconditions d'une classe de base ne doivent pas
être renforcées par une sous-classe et que les postconditions ne peuvent
pas être affaiblies dans les sous-classes.
31. 31
Preconditions
Une condition préalable doit être satisfaite avant qu'une méthode puisse être exécutée.
Regardons un exemple de précondition concernant les valeurs des paramètres :
public class Example {
// precondition: 0 < num <= 5
public void doSomeThing (int num) {
if (num <= 0 || num > 5) {
throw new IllegalArgumentException("Input out of range 1-5");
}
// some logic here...
}
}
32. 32
public class ExampleExtended extends Exemple{
@Override
// precondition: 0 < num <= 10
public void doSomeThing(int num) {
if (num <= 0 || num > 10) {
throw new IllegalArgumentException("Input out of range 1-10"); }
// some logic here...
}
}
Un sous-type peut affaiblir (mais pas renforcer) la condition préalable d'une méthode qu'il remplace.
33. 33
Une postcondition est une condition qui doit être remplie après l'exécution d'une
méthode.
Postconditions
public class Car {
protected int speed;
// postcondition: speed must reduce
protected abstract void brake(){
//implementation
}
}
public class HybridCar extends Car {
// Some properties and other methods...
@Override
// postcondition: speed must reduce
// postcondition: charge must increase
protected void brake() {
// Apply HybridCar brake
}
}
34. 34
Cette règle stipule que les types d'arguments de méthode de sous-type
remplacés peuvent être identiques ou plus larges que les types d'arguments de
méthode de supertype. C'est ce qu'on appelle la contravaeiance des argurments.
Règle de signature
Types d'arguments de méthode
Le type de retour de la méthode de sous-type substituée peut être plus
étroit que le type de retour de la méthode de supertype. C'est ce qu'on
appelle la covariance des types de retour. La covariance indique quand un
sous-type est accepté à la place d'un supertype.
Types d'arguments de méthode
35. 35
public class Example{
public Number generateNumber(){
//implementation
}
// Other Methods
}
La méthode generateNumber dans Example a pour type de retour Number. Remplaçons maintenant
cette méthode en renvoyant un type Integer plus étroit :
public class ExtendedExample extends Example{
@Override public Integer generateNumber()
{
return new Integer(10);
}
// Other Methods }
Puisque Integer IS-A Number, un code client qui attend Number peut remplacer Example par
ExtendedExampe sans aucun problème.
Si la méthode surchargée dans Example devait retourner un type plus large que Number, par ex. Object,
qui peut inclure n'importe quel sous-type d'objet, par ex. un camion. Tout code client qui s'appuyait sur
le type de retour Number ne pouvait pas gérer un camion !
36. 36
Règle de signature – Exceptions
La méthode de sous-type peut lever l’exception ou une exception dérivée (mais pas
d'exceptions plus larges ou supplémentaires) que la méthode de supertype.
Cela est compréhensible car lorsque le code client substitue un sous-type, il peut
gérer la méthode en lançant moins d'exceptions que la méthode du supertype.
Cependant, si la méthode du sous-type lève de nouvelles exceptions ou des
exception plus larges, cela casserait le code client.
38. ISP : Interface Segregation Principle
• Un client ne doit pas être forcé de dépendre de méthodes qu’il
n’utilise pas
• Préférer plusieurs interfaces spécifiques pour chaque client plutôt
qu'une seule interface générale :
• Créer des interfaces à grains fin qui sont spécifiques au client
38
39. ISP : Interface Segregation Principle
LSP
Le principe de séparation des interfaces (ISP) stipule qu'aucun client ne doit être contraint
de dépendre des méthodes qu'il n'utilise pas. Imaginez une interface avec de nombreuses
méthodes dans notre base de code et que beaucoup de nos classes implémentent cette
interface, bien que seules certaines de ses méthodes soient implémentées
39
40. public class SmtpMessage implements IMessage
{
public IList<String> ToAddresses ;
public string MessageBody ;
public string Subject;
public bool Send()
{
//Do the real work here
return true;
}
}
public interface IMessage
{
IList<String> ToAddresses ;
string MessageBody;
string Subject;
bool Send();
// Set Get
}
ISP : Interface Segregation Principle
40
41. public class SmsMessage implements IMessage
{
public IList<String> ToAddresses;
public string MessageBody;
public string Subject
public bool Send()
{
//Do the real work here
return true;
}
public String getSubject() throws UnsupportedOperationException
{}
public void setSubject(String Subject){} throws UnsupportedOperationException
{}
}
ISP : Interface Segregation Principle
41
43. public class SmtpMessage implements IEmailMessage
{
public IList<String> ToAddresses;
public IList<String> BccAddresses ;
public string MessageBody ;
public string Subject ;
public bool Send()
{
//Do the real work here
return true;
}
}
public class SmsMessage implements IMessage
{
public IList<String> ToAddresses ;
public string MessageBody ;
public bool Send()
{
//Do the real work here
return true;
}
}
ISP : Interface Segregation Principle
43
44. DIP : Dependency Inversion Principle
DIP – L’ EMERGENCE DES ABSTRACTIONS
44
45. • Les modules de haut niveau ne doivent pas dépendre de l’implementation de
modulesde plus bas niveau
• L'idée est que chaque point de contact entre deux modules soit
matérialisé par une abstraction
• Une abstraction se matérialise par une interface ou une classe de base qui est
aussi l’abstraction de classe plus élevé
DIP : Dependency Inversion Principle
45
46. public class NotifyUser {
public void Notify() {
WebConfigReader config = new WebConfigReader();
User user = config.GetUserToNotify();
DatabaseContext db = new DatabaseContext();
int userid = db.GetUserFromDatabase(user);
EmailClient eml = new EmailClient();
eml.send(userid);
}
}
Violation DIP
46
48. public class. NotifyUser
{
private IDatabaseContext _dbContextService;
private IEmailClient _emailClientService;
private IConfigReader _configReaderService;
public NotifyUser(IConfigReader reader, IEmaitCLient client, IDatabaseContext db)
{
configReaderService = reader;
emailClientService = client;
dbContextService = db;
}
public void Notify()
{
User user = _configReaderService.GetUserToNotify();
int userid = _dbContextService.GetUserFromDatabase(user);
emailClientService_send(userid);
}
}
DIP : Dependency Inversion Principle
48
49. Autres Principes
un principe de base du développement logiciel visant à réduire la
répétition d'informations. Le principe DRY est énoncé comme suit :
«Chaque élément de connaissance ou de logique doit avoir une
représentation unique et sans ambiguïté dans un système».
DRY : Don't Repeat Yourself
49
50. Conclusion
• Il est possible de ne pas appliquer tous les principes SOLID d’un coup,
il n’est pas choquant d’ajuster les choix de conception au moment où
l’on en a besoin (refactoring)
• La raison, le pragmatisme et l’expérience vont nous permettre
d’arbitrer.Avec l’expérience vous pourrez appliquer
systématiquement SOLID avec le même effort, mais probablement
pas dès le début
50