0% ont trouvé ce document utile (0 vote)
2 vues79 pages

JavaDesignPattern Seance 1 SOLID Principle

Le document présente les principes de conception orientée objet en Java, en se concentrant sur les design patterns, notamment le pattern Stratégie et le patron de méthode. Il explique l'importance des principes SOLID pour créer un code extensible et maintenable, en illustrant des exemples pratiques. Enfin, il met en avant la relation entre les objets et les comportements, ainsi que l'utilisation de l'héritage et de la composition dans la conception logicielle.

Transféré par

ayoub bouker
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
2 vues79 pages

JavaDesignPattern Seance 1 SOLID Principle

Le document présente les principes de conception orientée objet en Java, en se concentrant sur les design patterns, notamment le pattern Stratégie et le patron de méthode. Il explique l'importance des principes SOLID pour créer un code extensible et maintenable, en illustrant des exemples pratiques. Enfin, il met en avant la relation entre les objets et les comportements, ainsi que l'utilisation de l'héritage et de la composition dans la conception logicielle.

Transféré par

ayoub bouker
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Design Patterns en Java

Principes SOLID de conception orientée objet


Design patterns stratégie et patron de méthode

Olivier Goudet

Université d’Angers

03/09/2025
Introduction

I Connaı̂tre les bases de la programmation orientée objet


(héritage, classes abstraites et interfaces) ne suffit pas à faire
un code bien conçu.
I On pourra toujours faire un code qui marche mais l’extension
du code sera difficile, on risque de perdre du temps et il peut
y avoir des bugs... (surtout pour un gros logiciel).
I On souhaite garder au maximum le code souple et facilement
extensible si de nouvelles fonctionnalités doivent être ajoutées
par la suite.
Section 1

Interactions entre les objets


Interactions entre les objets

I Pour faire une bonne conception : penser à la façon dont les


objets vont interagir au moment de l’exécution.
I Une méthode représente un comportement que va avoir
l’objet une fois instancié.
I Comment les objets se comportent et dépendent les uns des
autres ?
I Deux principales relations entre les objets :
1. la relation d’héritage (”EST UN”)
2. la relation de composition (”A UN”)
I Il existe aussi une relation moins forte, dite de dépendance.
Diagramme de classes UML simplifié - relation d’héritage -
extension d’une classe existante
Un poisson est un animal.
Diagramme de classes UML simplifié - relation d’héritage -
implémentation d’une interface
Un avion est un objet volant.
La relation de composition

public class Vehicle {

//Composition
Engine engine;

public Vehicle(Engine engine){


[Link] = engine;
}

public void start(){


[Link]();
}

Un objet véhicule est composé d’un moteur. Un véhicule A UN


moteur.
Diagramme de classes UML simplifié avec une relation de
composition

Un véhicule A UN moteur.
La relation de dépendance

public class Printer {

public Printer(){
...
}

public void print(Journal journal){


[Link]([Link]());
}

Un objet Printer utilise dans une de ses méthodes un objet de type


Journal. Toute modification sur la classe Journal peut donc avoir
un impact sur la classe Printer.
Diagramme de classes UML simplifié avec une relation de
dépendance
Section 2

Principes SOLID de conception orientée objet


5 principes sous-jacents aux Design Patterns

Dans le livre Agile Software Development, Pinciples, Patterns and


Practices, Robert C. Martin a décrit en 2002 cinq principes
fondamentaux de conception (SOLID) :
1. Principe de responsabilité unique (Single Responsibility
Principle)
2. Principe ouvert/fermé (Open-Closed Principle)
3. Principe de substitution Liskov (Liskov substitution principle)
4. Principe de ségrégation des interfaces (Interface Segregation
Principle)
5. Principe d’inversion des dépendances (Dependency Inversion
Principle)
I) Principe ouvert/fermé

Les classes d’un logiciel doivent être ouvertes à l’extension mais


fermées à la modification.
Exemple

I Création d’un logiciel de simulation d’un hôpital avec des


employés de type infirmier et docteur.
Création des employés

public class Employee {

private int id;


private String name;
private String department;

public Employee(int id, String name, String department) {


[Link] = id;
[Link] = name;
[Link] = department;
}

public String toString() {


return ”Employee [id=” + id + ”, name=” + name + ”, department
=”
+ department + ”]”;
}

}
public class Nurse extends Employee{

public Nurse(int id, String name, String department) {


super(id, name, department);
[Link](”Nurse in action...”);
}

public class Doctor extends Employee{

public Doctor(int id, String name, String department) {


super(id, name, department);
[Link](”Doctor in action...”);
}

}
Gestion des actions des employés

I On souhaite maintenant avoir une méthode commune qui


permet de faire en sorte que chaque employé réalise un certain
nombre de tâches dans l’hôpital.
I Les tâches réalisées dépendent de la fonction de l’employé.
I Cette méthode pourra être appelée par exemple sur une liste
d’employés à chaque tour de la simulation.
1ère façon de faire

I On définit une classe de gestion de l’hôpital qui gère les


actions des employés.
I En fonction du type de l’employé reçu en paramètre, un
certain nombre d’actions sont réalisées.
Implémentation
public class HospitalManagement {

public void callUponEmployee(Employee employee){

if(employee instanceof Nurse){


checkVitalsSign();
drawBlood();
} else if (employee instanceof Doctor){
diagnosePatient();
prescribleMedicine();
}

public void checkVitalsSign(){...};


public void drawBlood(){...};
public void diagnosePatient(){...};
public void prescribleMedicine(){...};

Quel est le problème de conception que l’on rencontre ?


Problème de conception

I On va devoir changer la méthode callUponEmployee() de


HospitalManagement à chaque fois que l’on va ajouter de
nouveaux types d’employés dans l’hôpital.
I Cette classe n’est pas fermée à la modification.
I Cette classe va devenir vite très compliquée à comprendre et
déboguer car elles fait intervenir de nombreuses méthodes qui
définissent les comportements de tous les employés.
Meilleure conception en utilisant le Pattern Stratégie

I Quel est le point commun de ces employés infirmier et


docteur ? Ils font des tâches dans l’hôpital.
I Créer une classe de comportement des employés dans l’hôpital.
I Affecter aux différents employés des comportements
spécifiques.
Création d’une abstraction de comportement

C’est une interface qui définit de façon abstraite le comportement


d’un employé à l’hôpital (stratégie).

public interface BehaviorHospital {

public void work();

}
Deux types de comportement de travail
public class BehaviorNurse implements BehaviorHospital{

public void work() {


checkVitalsSign();
drawBlood();
}

public void checkVitalsSign(){...}


public void drawBlood(){...}
}

public class BehaviorDoctor implements BehaviorHospital{

public void work() {


diagnosePatient();
prescribleMedicine();
}

public void diagnosePatient(){...}


public void prescribleMedicine(){...}
}
On ajoute un comportement à la classe Employé
public class Employee {

private int id;


private String name;
private String department;
private BehaviorHospital behaviorHospital;

public Employee(int id, String name, String department) {


[Link] = id;
[Link] = name;
[Link] = department;
}

public void setBehavior(BehaviorHospital behaviorHospital){


[Link] = behaviorHospital;
}

public void performDuties(){


[Link]();
}
}
La classe de gestion de l’hôpital est maintenant très simple.

public class HospitalManagement {

public void callUponEmployee(Employee employee){


[Link]();
}

}
Test

public class EmergencyRoom {

public static void main(String args[]) {

HospitalManagement director = new HospitalManagement();


Employee jack = new Nurse(1, ”Jack”, ”emergency”);
[Link](new BehaviorNurse());

Employee suzan = new Doctor(2, ”Suzan”, ”emergency”);


[Link](new BehaviorDoctor());

[Link](jack);
[Link](suzan);

//On peut changer le comportement dynamiquement


[Link](new BehaviorDoctor());
[Link](jack);

}
}
On a vu différentes choses en fait

I Principe ouvert/fermé
I Notre deuxième conception est meilleure. La classe
HospitalManagement est maintenant fermée à la modification
I De plus les classes d’infirmier et de docteur sont maintenant
ouvertes facilement à l’extension. On peut leur affecter
dynamiquement de nouveaux comportements.
I Principe de conception : préférer si possible la composition à
l’héritage.
I Design Pattern Stratégie :
I Le Design Pattern Stratégie définit une famille d’algorithmes,
encapsule chacun d’eux et les rend interchangeables.
I Le Design Pattern Stratégie permet à l’algorithme de varier
indépendamment des clients qui l’utilisent.
Design Pattern Stratégie pour la modélisation des
comportements des employés de l’hôpital
Design Pattern Stratégie - diagramme général
Exemple d’implémentation du design pattern Stratégie
directement intégré dans la librairie Java

I Beaucoup de design patterns sont déjà intégrés dans la


librairie Java.
I Par exemple, la méthode [Link]() prend un objet de
type Comparator en entrée. Cet objet de type Comparator
définit la ”stratégie” de tri que l’on souhaite employer.
I On peut implémenter différentes classes de comparateurs qui
implémentent cette même interface Comparator.
Exemple - tri de listes d’étudiants suivant leur numéro
d’étudiant ou bien leur nom
public class Student {

private int studentID;


private String name;

public Student(int studentID, String name) {}


[Link] = studentID;
[Link] = name;
}

public int getStudentID() {...}

public String getName() {...}

public String toString() {


return ”Student − name : ” + [Link] + ” − ID : ” + this
.studentID;
}

}
1ère ”Stratégie” - comparaison suivant le numéro
d’étudiant

import [Link];

public class ComparatorID implements Comparator<Student>{

public int compare(Student o1, Student o2) {

return [Link]() − [Link]();


}

}
2ème ”Stratégie” - comparaison suivant le nom de
l’étudiant

import [Link];

public class ComparatorName implements Comparator<Student>{

public int compare(Student o1, Student o2) {

return [Link]().compareTo([Link]());

}
Différents tris possibles avec ces comparateurs
public class TestStudent {

public static void main(String[] args) {

ArrayList<Student> studentList = new ArrayList<Student>();


[Link](new Student(345654, ”Pierre”));
[Link](new Student(110225, ”Paul”));
[Link](new Student(998984, ”Jacques”));

[Link](studentList, new ComparatorID());


[Link](”Sort by student ID”);
for(Student student : studentList){
[Link](student);
}

[Link](studentList, new ComparatorName());


[Link](”Sort by student name”);
for(Student student : studentList) {
[Link](student);
}
}
}
Sortie console

Sort by student ID
Student - name : Paul - ID : 110225
Student - name : Pierre - ID : 345654
Student - name : Jacques - ID : 998984
Sort by student name
Student - name : Jacques - ID : 998984
Student - name : Paul - ID : 110225
Student - name : Pierre - ID : 345654
Diagramme de classes UML simplifié
Collection/Comparator

I Variante du design pattern Stratégie avec une relation de


dépendance plutôt que de composition.
I Dans ce cas, l’objet de type Collection n’est pas directement
composé d’une objet de type Comparator mais en utilise un
via sa méthode sort().
II) Principe d’inversion des dépendances

1. Les modules de haut niveau ne doivent pas dépendre des


modules de bas niveau.
2. Les abstractions ne doivent pas dépendre des détails. Ce sont
les détails qui doivent dépendre des abstractions.
Exemple

On a vu ce principe dans l’exemple avec l’hôpital.


Ici Employee dépend de l’abstraction de comportement
BehaviorHospital.
public class Employee {

...

public void setBehavior(BehaviorHospital behaviorHospital){


[Link] = behaviorHospital;
}

public void performDuties(){


[Link]();
}
}
Exemple

I En général les classes abstraites et les interfaces ne changent


pas aussi souvent que leurs implémentations concrètes.
I Ici la classe Employee dépend d’un comportement abstrait
donc est robuste au changement, car on pourra facilement
intégrer de nouveaux comportements sans changer son code.
I Principe général de conception : ne pas dépendre de choses
qui changent souvent.
Dans la même logique : le pattern Patron de méthode

I Le patron de méthode définit le squelette d’un algorithme dans


une méthode, en délégant certaines étapes aux sous-classes.
I Patron de méthode permet aux sous-classes de redéfinir
certaines étapes d’un algorithme sans modifier la structure de
celui-ci.
I Les sous-classes sont les détails. elles dépendent de
l’abstraction (l’algorithme général).
Diagramme UML : Patron de méthode
Exemple

I On définit un objet de type Order qui contient la commande


d’un client définie par une liste d’éléments avec leurs prix.
I On veut implémenter deux types d’objet Printer qui
permettent d’afficher tous les éléments de cette commande
ainsi que son total soit en format html, soit en format texte.
I Un patron de méthode va permettre ici de définir de façon
abstraite les étapes générales de l’affichage qui sont
communes.
I Chaque objet HtmlPrinter et TextPrinter implémente ensuite
des spécificités de ces affichages (format html ou format
texte).
Exemple : affichage de commandes de clients en format
html ou texte
Classe Order
public class Order {

private Map<String, Double> items = new HashMap<>();


private double total;

public Order() { total = 0.0;}

public Map<String, Double> getItems() {


return items;
}

public void addItem(String name, double price) {


[Link](name, price);
total+= price;
}

public double getTotal() {


return total;
}

}
Classe abstraite qui définit le patron de méthode
OrderPrinter

public abstract class OrderPrinter {

public final void printOrder(Order order, String filename) throws


IOException {
try(PrintWriter writer = new PrintWriter(filename)){
[Link](start());
[Link](formatItems(order));
[Link](formatTotal(order));
}
}

protected abstract String start();


protected abstract String formatItems(Order order);
protected abstract String formatTotal(Order order);

}
Classe concrète TextPrinter
public class TextPrinter extends OrderPrinter{

protected String start() {


return ”Order Details”;
}

protected String formatItems(Order order) {


StringBuilder builder = new StringBuilder(”Items\n
−−−−−−−−−−−−−−−−−−−−−−−−−−−\n”);
for([Link]<String, Double> entry : [Link]().
entrySet()) {
[Link]([Link]()+ ” $”+entry.
getValue()+”\n”);
} [Link](”−−−−−−−−−−−−−−−−”);
return [Link]();
}

protected String formatTotal(Order order) {


return ”Total: $”+[Link]();
}
}
Classe concrète HTMLPrinter
public class HtmlPrinter extends OrderPrinter {

protected String start() {


return ”<html><head><title>Order Details</title></head><body>”;
}

protected String formatItems(Order order) {


StringBuilder builder = new StringBuilder(”<p><ul>”);
for([Link]<String, Double> e : [Link]().entrySet()) {
[Link](”<li>”+[Link]()+” $”+[Link]()+”</li>”);
}
[Link](”</ul></p>”);
return [Link]();
}

protected String formatTotal(Order order) {


return ”<br/><hr/><h3>Total : $”+[Link]()+”</h3></
body></html>”;
}
}
Test
public class Client {

public static void main(String[] args) throws IOException {

Order order = new Order();

[Link](”Soda”, 2.50);
[Link](”Sandwitch”, 11.95);
[Link](”Pizza”, 15.95);

//Ecriture de la commande en format Html


OrderPrinter printer1 = new HtmlPrinter();
[Link](order, ”[Link]”);

//Ecriture de la commande en format texte


OrderPrinter printer2 = new TextPrinter();
[Link](order, ”[Link]”);
}

}
III) Principe de responsabilité unique

I Une classe doit avoir une seule raison de changer.


I Séparation des tâches : différentes classes doivent gérer
différents problèmes/tâches.
Exemple

I Création d’un logiciel d’archivage de journaux.


Création d’une classe Journal
class Journal
{
private ArrayList<String> entries = new ArrayList<>();

private static int count = 0;

public void addEntry(String text)


{
[Link](”” + (++count) + ”: ” + text);
}

public void removeEntry(int index)


{
[Link](index);
}

public String toString() {


return [Link]([Link](), entries);
}

}
Demande d’extension

I On veut ajouter des fonctionnalités de sauvegarde et


chargement de ce journal.
1ère façon de faire

On ajoute des méthodes save() et load() dans la classe Journal.

class Journal
{
//other methods

public void save(String filename) throws Exception {


try (PrintStream out = new PrintStream(filename)) {
[Link](toString());
}
}

public void load(String filename) {}


public void load(URL url) {}
}

Quels sont les problèmes que l’on peut rencontrer ?


I Le code fonctionne mais c’est une violation du principe de
responsabilité unique.
I L’objet Journal a la responsabilité d’enregistrer du texte, mais
aussi la responsabilité de gérer les sauvegardes/chargements.
I Quels sont les problèmes que l’on risque de rencontrer ?
I On risque d’arriver à une classe Journal très longue, difficile à
comprendre et à déboguer.
I On risque de répéter le code si on a une dizaine d’autre objets
qui ont besoin d’être sauvegardés/chargés.
I Si on veut changer la méthode de sauvegarde, il faudra la
changer dans toutes les classes.
2ème façon de faire qui respecte le principe de
responsabilité unique

Mieux vaut séparer les fonctionnalités :


I la classe Journal gère seulement l’enregistrement du texte.
I Une nouvelle classe gère seulement les fonctionnalités de
sauvegarde/chargement des objets composés de texte.
Ajout d’une classe LoadSaveHandler qui a les méthodes de
sauvegarde et de chargement des fichiers.

class LoadSaveHandler
{

public void saveToFile(Journal journal, String filename) throws


Exception
{
try (PrintStream out = new PrintStream(filename)) {
[Link]([Link]());
}
}

public void load(Journal journal, String filename) {}


public void load(Journal journal, URL url) {}

}
Lancement du programme

class Test
{

public static void main(String[] args) throws Exception


{

Journal j = new Journal();


[Link](”My name is John”);
[Link](”I am in good form today”);
[Link](j);

LoadSaveHandler lsh = new LoadSaveHandler();


String filename = ”path/[Link]”;
[Link](j, filename);

}
}
IV) Principe de substitution Liskov

I Il doit toujours être possible de substituer un type de base par


un sous-type.
I Lorsque l’on utilise l’héritage, il faut faire attention
notamment à des redéfinitions de méthodes qui pourraient
être incohérentes.
Création d’une classe Rectangle
class Rectangle{

protected int width, height;

public Rectangle() {}

public Rectangle(int width, int height) {


[Link] = width;
[Link] = height;
}

public void setWidth(int width) {


[Link] = width;
}

public void setHeight(int height) {


[Link] = height;
}

public int getArea() { return width∗height; }


}
Création de Carré, une sous-classe de Rectangle
class Square extends Rectangle
{
public Square() {
}

public Square(int size) {


width = height = size;
}

public void setWidth(int width) {


[Link](width);
[Link](width);
}

public void setHeight(int height) {


[Link](height);
[Link](height);
}
}
Ajout d’une méthode qui utilise des objets Rectangle

class Demo
{
static void useIt(Rectangle r)
{
int width = [Link]();
[Link](10);
// area = width ∗ 10
[Link](”Expected area of ” + (width∗10) + ”, got ” + r.
getArea());
}

}
Test de cette méthode

public static void main(String[] args) {

Rectangle rc = new Rectangle(2, 3);


useIt(rc);

//Problem
Rectangle sq = new Square();
[Link](5);
useIt(sq);

Console
Expected area of 20, got 20
Expected area of 50, got 100
D’où vient le problème ?

I La méthode setHeight est redéfinie par la sous-classe carré.


Pour un carré quand setHeight est appelée, sont mis à jour la
hauteur mais aussi la largeur, de façon à conserver un carré.
I Ce qui peut paraı̂tre logique, mais crée un comportement
imprévu quand la méthode useIt() est appelée.
I Attention à l’héritage qui peut produire des bugs (ici à cause
d’une redéfinition incohérente de méthode).
I Comment faire si on veut toujours continuer à utiliser cette
méthode useIt() mais toujours avoir la notion de carré
différente de celle de rectangle ?
Autre conception : éviter de créer une sous-classe carré

I On ajoute seulement une méthode isSquare() dans la classe


Rectangle.
I isSquare() permet de savoir quand on a un carré ou un
rectangle (par exemple pour appliquer des fonctions
différentes pour chaque type).

class Rectangle{

...

public boolean isSquare()


{
return width == height;
}

}
Création de carrés et rectangles avec une RectangleFactory

cf. design pattern Factory que l’on reverra en détail plus tard.

class RectangleFactory
{
public static Rectangle newRectangle(int width, int height)
{
return new Rectangle(width, height);
}

public static Rectangle newSquare(int side)


{
return new Rectangle(side, side);
}

}
Test de cette méthode

public static void main(String[] args) {


Rectangle rc = [Link](2, 3);
useIt(rc);

Rectangle sq = [Link](5);
useIt(sq);
}

Console
Expected area of 20, got 20
Expected area of 50, got 50
V) Principe de séparation des interfaces

1. Ne pas mettre trop de fonctions dans une seule interface


(juste ce qui est nécessaire).
2. Plutôt séparer en différentes interfaces.
Classe Document et interface Machine
I Classe Document

class Document{

...

I Interface Machine

interface Machine
{

void print(Document d);


void fax(Document d) throws Exception;
void scan(Document d) throws Exception;

}
Ok de définir cette interface générale si on a besoin d’une
machine multifonction
class MultiFunctionPrinter implements Machine
{

public void print(Document d)


{
//
}

public void fax(Document d)


{
//
}

public void scan(Document d)


{
//
}

}
Mais si on a besoin de définir une imprimante seulement...

class OnlyPrinter implements Machine


{
public void print(Document d)
{
//
}

public void fax(Document d) throws Exception


{
throw new Exception();
}

public void scan(Document d) throws Exception


{
throw new Exception();
}
}
Problème que l’on rencontre

I Ce n’est pas très cohérent car on doit lever des exceptions


pour les méthodes non-implémentées.
I Mieux vaut dans ce cas séparer les différentes fonctionnalités.
I Faire cette séparation au cas par cas si besoin. Attention à ne
pas multiplier non plus inutilement les interfaces.
Différentes interfaces pour chaque fonction

interface Printer
{

void Print(Document d);

interface IScanner
{

void Scan(Document d);

}
Implementation en fonction des besoins
class JustAPrinter implements Printer{

public void Print(Document d){


//
}

class Photocopier implements Printer, IScanner


{
public void Print(Document d) {
//
}

public void Scan(Document d) {


//
}

}
On peut aussi définir une sous-interface

interface MultiFunctionDevice extends Printer, IScanner


{

}
Définir une machine multi-fonction composée de différents
modules
class MultiFunctionMachine implements MultiFunctionDevice
{
private Printer printer;
private IScanner scanner;

public MultiFunctionMachine(Printer printer, IScanner scanner)


{
[Link] = printer;
[Link] = scanner;
}

public void Print(Document d) throws Exception{


[Link](d);
}

public void Scan(Document d) throws Exception{


[Link](d);
}
}
Ce qu’il faut retenir - principes SOLID
I Principe de responsabilité unique (Single Responsibility
Principle)
I Une classe doit avoir une seule raison de changer
I Séparation des tâches: différentes classes gèrent différents
problèmes indépendants
I Principe ouvert/fermé (Open-Closed Principle)
I Les classes doivent être ouvertes à l’extension mais fermées à
la modification
I Principe de substitution Liskov (Liskov substitution principle
I Il doit toujours être possible de substituer un type de base par
un sous-type
I Principe de ségrégation des interfaces (Interface Segregation
Principle)
I Ne pas mettre trop de méthodes dans une seule interface
(juste ce qui est nécessaire). Séparer en différentes interfaces
pour implémenter différentes fonctionnalités indépendantes
I Principe d’inversion des dépendances (Dependency Inversion
Principle)
I Les modules de haut niveau ne doivent pas dépendre des
modules de bas niveau
Ce qu’il faut retenir - principe général de conception

I Principe de conception : préférer si possible la composition à


l’héritage
I A-Un peut être préférable à EST-UN
Ce qu’il faut retenir - Design Pattern Stratégie

I Diagramme de classes UML simplifié :


Ce qu’il faut retenir - Design Pattern Patron de méthode

I Diagramme de classes UML simplifié :

Vous aimerez peut-être aussi