BPOO
BPOO
Introduction
À la différence de la programmation impérative, un programme
écrit dans un langage objet répartit l’effort de résolution de problèmes sur
un ensemble d’objets collaborant par envoi de messages. Chaque objet se
décrit par un ensemble d’attributs (caractéristiques de l'objet) et un
ensemble de méthodes portant sur ces attributs (fonctionnalités de l'objet).
La POO résulte de la prise de conscience des problèmes posés par
l’industrie du logiciel ces dernières années en termes de complexité accrue
et de stabilité dégradée. L'objet solutionne certains de ces problèmes à
travers :
• l'encapsulation des attributs qui empêche toute modification
externe accidentelle
• l'héritage qui permet la ré utilisabilité du code
Il est inconséquent d’opposer la programmation impérative à l’OO car, in
fine, toute programmation des méthodes reste tributaire des mécanismes
de programmation procédurale et structurée. On y rencontre des
variables, des arguments, des boucles, des arguments de fonction, des
instructions conditionnelles, tout ce que l’on trouve classiquement dans
les boîtes à outils impératives.
5 / 56 6 / 56
• IDE : IntelliJ IDEA ou autre (Eclipse, NetBeans, etc.) J. Blosch. Effective Java, 3rd Edition
Addison-Wesley Professional : 2018.
• Les tests : JUnit (version 5)
R.C. Martin.
• Versioning : Git et GitHub
Clean Code - A Handbook of Agile Software Craftmanship
Prentice Hall : 2008.
...et pour commencer, au moins quelques principes de
E. Freeman, E. Robson, B. Bates, K. Sierra.
programmation :
Head First - Design Patterns (régulièrement mis à jour)
KISS - Keep It Simple and Stupid O’Reilly : 2014.
YAGNI - You Ain’t Gonna’ Need It
DRY - Don’t Repeat Yourself Pensez à consulter régulièrement la documentation officielle Java :
https://docs.oracle.com/en/java/javase/11/docs/api/
index.html
7 / 56 8 / 56
Java Pourquoi Java dans ce cours ?
Java est un langage de programmation
• Simple à prendre en main
• langage de haut niveau, objet, portable, ...
- l’API officielle très riche et assez fiable,
• langage pseudocompilé (bytecode) exécuté par la JVM - langage compilé et robuste :
langage fortement typé
avec un mécanisme puissant de gestion d’exceptions
- pas très verbeux
- gestion automatique de la mémoire :
Java est aussi une plateforme utilisation implicite des pointeurs (références)
plus de sécurité
le ramasse-miettes (garbage collector )
• plateforme portable (Write once, run anywhere)
• interface de programmation d’application (API) • framework de tests unitaires complet : JUnit
- Swing, AWT, JavaFX
• un des langages orientées objets les plus répandus au monde
- JDBC
- Réseau, HTTP, FTP
- IO, math, config, etc. Attention : ce cours aurait pu très bien avoir un autre langage
orientée objet comme support (C++, C#, Python, etc.)
9 / 56 10 / 56
11 / 56 12 / 56
Objets - première immersion Classe vs Objets
Définition (une autre)
Objet = identité + état + comportement
Définition
Exemple d’objet Voiture : Une classe est une description abstraite d’un ensemble d’objets ”de
• Identité : numéro d’identification ou code-barres etc. même nature”. Ces objets sont des instances de cette classe.
• État : marque, modèle, couleur, vitesse. . .
• La classe est un ”modèle” pour la création de nouveaux objets.
• Services rendus par l’objet : Démarrer, Arrêter, Accélérer,
Freiner, Climatiser, . . . • ”Contrat” garantissant les compétences de ses objets.
15 / 56 16 / 56
Construction des objets Construction par défaut
Dans un programme Java, toute variable doit être initialisée avant Si le développeur n’a écrit aucun constructeur, alors le compilateur
d’être utilisée. fournit un constructeur sans argument qui consiste à initialiser tous
les champs avec leur valeur par défaut.
Pour les objets on parle de construction. class Point2D {
int x, y;
class Voiture { class Voiture { class Point2D {
private String marque; private String marque; public Point2D() {
int x, y;
private String modele; private String modele; x = 0;
class App {
public void arreter() { public static void main(String args[]) {
/* Du code */
} // Création d’un point
} Point2D petitPoint = new Point2D();
19 / 56 20 / 56
Petite recap – vocabulaire Cycle de vie des objets
Temps
21 / 56 22 / 56
}
}
}
mesVoitures.add(v); 2. tout ce qui est non marqué est désigné ”garbage”
public class App { public void vendreVoiture(){ 3. désignation des cellules mémoires non marquées comme
public static void main(String args[]) { Voiture v = mesVoitures.get(0);
Concessionnaire treNaud = new Concessionnaire("treNaud", 125360); soldeCompte += v.getPrix(); disponibles à la réutilisation (on peut écraser leur contenu)
// méthode où un objet de type Voiture sera créé // on va enlever de la liste la première
treNaud.commanderVoiture(); // occurrence de la référence v
treNaud.vendreVoiture(); }
mesVoitures.remove(v);
• Important : dès lors que l’objet n’est plus accessible dans le
}
/* À la fin de l’exécution de la méthode vendreVoiture(), un objet code, il faut supposer qu’il est détruit.
de type Voiture ne sera plus accessible. Donc, l’espace mémoire
qu’il occupe sera rendu disponbile par le Garbage Collector. */
}
}
23 / 56 24 / 56
Classe–Type–Objet Typage Java
Définition
Un type de données définit : En Java toutes les données sont typées. Il y a deux sortes de types :
• un domaine de valeurs (associé aux données de ce type) • types primitifs (prédéfinis)
• un ensemble d’opérations applicables aux données de ce type • types objets (référence)
int n = 40; // n est de type int type primitif taille en octets valeur par défaut
n = n + 2; // l’opération + est applicable sur les int boolean 1 bit false Accès par valeur pour les types primitifs :
boolean b = true; // b est de type boolean byte 1 0
b = true/3: // opération non-valide car la division n’est pas applicable sur les boolean short 2 0 int x, y;
int 4 0 x = 2;
Une classe définit en fait un type d’objets : long 8 0 y = 3;
float 4 0.0
• un ensemble de propriétés (des attributs) double 8 0.0 // on compare la valeur de x à la valeur de y
x == y;
char 2 ’\u0000’
• un ensemble de comportements applicables (des opérations)
Voiture L’accès aux types objets se fait par référence :
attributs -marque: string
-modele: string Voiture v; // v est de type Voiture
Voiture x, y; // deux objets de type Voiture
-dateFabrication: Date x = new Voiture();
v.demarrer(); // méthode applicable sur un type Voiture
+demarrer(): void y = new Voiture();
+arreter(): void x == y; // on compare les adresses mémoire de x et de y
v.manger(); // méthode non-applicable sur un type Voiture
Operations
25 / 56 26 / 56
27 / 56 28 / 56
Utilité des types primitifs Typage Java : représentation en mémoire
L’esprit de la programmation orientée objet est que tout est objet.
Comment une variable x est représentée en mémoire ?
à quoi bon avoir créé des types primitifs ? ? ?
Cas 1 type primitif : x = valeur
Que fait-il ? @i
@j valeur
class App {
public static void main(String args[]) { x @k
@l
Integer somme = 0;
for (int i = 0; i < 100000; i++)
somme += i; Cas 2 type référence : x = objet
System.out.println(somme); @i
} @j @n objet
@n
} x @k
@l
Inconvénients ?
29 / 56 30 / 56
2. si la variable est de type référence, alors la méthode manipule une class App { class App {
/* Du code ici */ /* Du code ici */
copie de l’adresse (indiquant où est situé l’objet concerné). Donc : public static void main(String args[]) { public static void main(String args[]) {
Voiture maCaisse = new Voiture("cabriolet"); Voiture maCaisse = new Voiture("cabriolet");
cette adresse est non-modifiable (car passage par valeur) mais System.out.println(maCaisse); // resultat ? System.out.println(maCaisse); // resultat ?
Usine1 u1 = new Usine1(); Usine2 u2 = new Usine2();
l’objet référencé, lui, est accessible, et donc modifiable à travers son u1.customizer(maCaisse);
System.out.println(maCaisse); // resultat ?
u2.cusomizer(maCaisse);
System.out.println(maCaisse); // resultat ?
interface publique. }
}
}
}
33 / 56 34 / 56
public Compte(String n, String p) { public Compte(String nom, String prenom) { public Compte(String nom, String prenom) {
nom = n; this.nom = nom; this(nom); // appel du constructeur Compte(String)
prenom = p; this.prenom = prenom; this.prenom = prenom;
soldeCompte = 0; soldeCompte = 0; }
} }
public Compte(String nom, String prenom, double soldeCompte) {
public void rechargerCompte(double somme) { public void rechargerCompte(double soldeCompte) { this(nom, prenom); // appel du constructeur Compte(String, String)
soldeCompte = somme; this.soldeCompte = soldeCompte; this.soldeCompte = soldeCompte;
} } }
// méthode spécifique Java pour l’affichage // méthode spécifique Java pour l’affichage public void rechargerCompte(double soldeCompte) {
public String toString() { public String toString() { this.soldeCompte = soldeCompte;
return "[" + prenom + return "[" + prenom + }
","+nom+","+soldeCompte +"]"; ","+nom+","+soldeCompte +"]"; }
} }
} }
Pensez à faire collaborer les constructeurs pour éviter la duplication
de code !
35 / 56 36 / 56
Environnement Java Environnement Java - les packages
De point de vue physique un programme Java est :
• un ensemble de fichiers sources .java Remarque
• un ensemble de fichiers compilés .class (le En Java chaque classe appartient nécessairement à un package.
byte-code) correspondants aux sources
• le tout organisé dans des répertoires : • les packages permettent un découpage de l’espace de nommage
- pensez à séparer le byte-code des sources en plusieurs sous-espaces de noms.
• un package est un ensemble cohérent de classes pouvant être
De point de vue logique un programme Java est un ensemble de importées toutes ensembles comme individuellement.
classes organisées dans des packages (ou paquetages).
• package par défaut : unnamed (son utilisation est fortement
déconseillée)
• le nom complet d’une classe (Fully Qualified Class Name ou
fichier .class FQCN) est constitué du nom du package auquel elle appartient
suivi de son nom. Par exemple :
classe Java
java.util.ArrayList // la classe ArrayList située dans le package java.util
37 / 56 38 / 56
39 / 56 40 / 56
Encapsulation Encapsulation – visibilité
Une classe a deux aspects :
En Java, il y a 4 niveaux de visibilité :
1. son interface : vue externe (ne pas confondre avec le mot-clé ”interface” en Java)
class Toto {
2. son corps : implémentation des méthodes et des attributs private int u; // seules les méthodes de la classe Toto y ont accès
L’encapsulation est un principe de conception et de programmation protected int x; // accessible aux membres de la classe Toto, de ses sous-classes
//et de toutes les classes situées dans le m^
eme paquetage
consistant à distinguer l’interface et le corps d’une classe. public int y; // accessible aux membres de toutes les classes
}
• un mécanisme de visibilité permet de contrôler les accès au
corps d’un objet
Remarques :
• seules les méthodes doivent pouvoir manipuler l’état interne de
La notion de sous-classe sera
l’objet et servent à la communication inter-objets vue ultérieurement.
Principe fondamental de la POO Ici ClasseA et ClasseB ont
L’utilisateur/programmeur ne connaı̂t que l’interface de l’objet. les mêmes accès aux
L’implémentation de sa classe est masquée et non accessible à membres de la classe Toto
l’utilisateur.
41 / 56 42 / 56
return marge;
}
Pensez au principe YAGNI (You Ain’t Gonna’ Need It) :
public static void setMarge(double v) {
protégez-vous de vos propres bêtises (et de celles de futurs marge = v; tablette.setMarge(3.6); // instruction qui fonctionne
} // mais qui est INTERDITE
collaborateurs) }
43 / 56 44 / 56
Membres et méthodes static Mot-clef final
Être déclaré en static est une contrainte très forte : Pseudo-définition
• aucune dynamique : pas de possibilité d’avoir une valeur
Une entité final ne peut être instanciée qu’une seule fois.
d’attribut différente pour chaque objet de même type
• pour une variable ou un attribut : une fois la valeur affectée,
• les méthodes static peuvent manipuler que des attributs
elle ne pourra plus être modifiée. Exemple :
static. Erreur du débutant : déclarer tout en static, comme
ça on n’en parle plus ! /
public void test1() { public void test2() {
final int x = 10; final Voiture v = new Voiture("berline");
v.setCouleur(Color.BLACK); // fonctionne car on change l’état
System.out.println(x); // affiche 10 // mais pas l’objet lui-m^
eme
• la méthode main(String) de la classe principale est static
x = 25; // ERREUR de compilation v = new Voiture("berline"); // ERREUR de compilation
car c’est le point d’entrée du programme x++; // ERREUR de compilation v = new Voiture("cabriolet"); // ERREUR de compilation
} }
• on utilise souvent la clause static final pour déclarer des
• si la variable/attribut est une référence vers un objet, alors on
constantes :
peut modifier son état interne (à travers ses méthodes), mais la
public static final double PI = 3.14 ;
référence de l’objet sera la même
Moralité • les classes et les méthodes peuvent aussi être déclarées final
L’utilisation des membres static doit être justifiée. - classe final : ne peut pas être ”sous-classée” (à voir plus tard dans le cours)
- méthode final : ne peut pas être ”redéfinie” dans une ”sous-classe”
45 / 56 46 / 56
51 / 56 52 / 56
Qu’en pensez-vous ?
import java.awt.Color;
53 / 56 54 / 56
https://xkcd.com/1513/
55 / 56 56 / 56
Héritage : respect du contrat Héritage : exemple avec l’API Java
• l’héritage est une relation transitive de type ”EST UN” : si la En Java toutes les classes héritent d’une classe spéciale : Object
classe A hérite de la classe B et B hérite de la classe C, alors A Object
hérite aussi de C +toString(): void
+equals(o: Object): void
+hashCode(): int
• La sous-classe est liée par le ”contrat” de sa classe parente #finalize(): void
+getClass(): Class
- elle ne peut pas restreindre la visibilité d’une méthode de la
...
classe parente :
code OK code interdit à la compilation
public class B { public class A extends B {
public void maMethode(){ private void maMethode(){
String Number System
... Runtime Thread Class
... Throwable
// corps // corps
} }
} }
public void setNom(String nom) { public void attribuerNumero(int n) { // instanciation d’un objet de type Personnel en tant qu’Enseignant
this.nom = nom; numero = n; Personnel membre = new Enseignant();
} } membre.setNom("Toto");
} } // instanciation d’un objet de type Enseignant en tant qu’Enseignant
Enseignant prof = new Enseignant();
prof.setNom("Lolo"); // utilisation de la méthode publique héritée de Personnel
prof.attribuerNumero(10059392);
public class App {
}
public static void main(String[] args) { }
9 10
11 12
Polymorphisme Polymorphisme : illustration
• parfois certaines méthodes de la classe mère nécessitent une public class FormeGeometrique {
private double centre;
// on suppose avoir défini les coordonnées de chaque point pour décrire la forme géometrique
certaine adaptation, il est donc possible des les redéfinir.
public void calculerSurface() {
// calcul intégral en utilisant les coordonnées
FormeGeometrique }
-centre: Point }
+calculerSurface()
public class Rectangle extends FormeGeometrique { public class Cercle extends FormeGeometrique {
private double hauteur, largeur; private double rayon;
un objet de type Cercle , c’est la méthode de Cercle qui forme = new Rectangle();
forme.calculerSurface(); // la méthode de calcul de Rectangle
sera invoquée (et pas celle de la classe mère). }
}
13 14
Le polymorphisme est réalisable grâce à la Liaison Dynamique : • la surcharge d’une méthode a lieu lorsqu’on définit une nouvelle
Liaison dynamique méthode ayant le même nom, mais avec :
1. un nombre différents de paramètres et sinon,
Mécanisme permettant que la définition d’une méthode soit décidée au 2. les types de paramètres différents dans l’ordre (de gauche à
moment de l’exécution de celle-ci (et non à la compilation). droite)
=⇒ Introspection de type • cas que vous avez déjà vu : la surcharge du constructeur
15 16
Polymorphisme : exemples en Java equals(Object o) et hashCode()
Par défaut dans Object la méthode equals(Object o) fait la comparaison
L’annotation @Override indique au compilateur qu’on est en train de la plus ”naı̈ve” :
redéfinir une méthode de la classe parente
public class Object {
=⇒ s’il y a incohérence (erreur dans le nom, ou paramètres), le // du code de Object ici ...
compilateur va le signaler par une erreur public boolean equals(Object obj) {
return this == obj;
La classe Object possède plusieurs méthodes pratiques mais trois d’entre }
}
elles souvent sont à redéfinir :
• int hashCode() affecte un code ”pseudo-unique” à this afin de La situation est similaire pour hashCode() : typiquement l’adresse de this
est convertie en un entier (mais ça dépend de la JVM...)
permettre de l’identifier numériquement
• String toString() : une chaı̂ne de caractères censée décrire l’objet Pensez toujours à redéfinir ces deux fonctions en même temps
dans vos classes afin de respecter le contrat de Object :
par défaut : nom de la classe + @ + hash code obtenu à partir des
valeurs de chaque attribut de l’objet −→ peu lisible • equals(Object o) définit une relation d’équivalence
• si l’état de l’objet n’est pas modifié alors plusieurs appels à
• boolean equals(Object o) compare this à o de façon à
hashCode() retournent la même valeur
garantir une relation d’équivalence
• si les hash codes sont différents alors equals(0bject o)
retourne false (l’inverse n’est pas nécessairement vrai)
17 18
@Override
public int hashCode() { Humain h1 = null; // On déclare l’objet de type Humain
return Objects.hash(nrINSEE, nom, echelon, base, nbHeures); Humain h2 = new Femme("Toto"); // On l’instancie avec un type concret
// on peut choisir sa propre fonction de hachage System.out.println(h2); // affiche "mon nom c’est : Toto"
} Humain h3 = new Humain(); // ERREUR de compilation
19 20
Classe abstraite Classe abstraite : illustration en Java
Définition public abstract class Medecin {
private String nom;
Une classe abstraite est une classe qui ne peut pas être instanciée. private int numeroID; «abstract»
Medecin
public void ecrireOrdonance() { /* du code ici */ } -nom: String
-numeroId: int
- sert uniquement de super classe à d’autres classes public void prendreTension() { /* du code ici */ } +ecrireOrdonance()
+prendreTension()
public abstract void soigner(); // méthode abstraite «abstract»+soigner()
- peut contenir des méthodes abstraites }
- ses sous-classes doivent définir toutes les méthodes abstraites public class Chirurgien extends Medecin {
private String scalpel;
(ou être abstraites elles-mêmes) public void soigner() { Generaliste Chirurgien Ophtalmologue
System.out.println("J’utilise un " + -stethoscope -scalpel -refracteur
«abstract» scalpel + " et j’opére");
+soigner() +soigner() +soigner()
Medecin }
}
-nom: String
Opérations -NumeroId: Integer
"normales" +ecrireOrdonance() public class Generaliste extends Medecin {
+prendreTension() private String stethoscope;
«abstract»+soigner() public void soigner() { System.out.println("J’utilise un " + stethoscope + " et je décide le soin"); }
}
21 22
- et ça sera aux sous-classes de s’y conformer ou de la redéfinir public Livre(String nom, double royalties) { public class AppProduit {
this.royalties = royalties; public static void main(String[] args) {
this.nom = nom; Produit p1 = new Smartphone("Poire", 989.99, 10);
} Produit p2 = new Livre("Tom Sawyer", 35);
Quel est alors l’intérêt des classes abstraites sans méthodes System.out.println("p1 : " + p1.getPrixFacture() +
public double getPrix() { "..., cher pour ce que c’est...");
abstraites ? return royalties + 10; System.out.println("p2 : " + p2.getPrixFacture());
} }
} }
23 24
Interfaces en Java Exemple d’interface : java.util.List
Rappel
«interface»
Classe ≈ nom + attributs + opérations List
+add()
+remove()
+contains(): boolean
+isEmpty(): boolean
• Une interface Java est équivalente à une classe «interface»
MonInterface +size(): int
réalisation de Comparable .
27 28
Classe cliente d’une interface Héritage : interface
• Quand une classe dépend d’une interface pour réaliser ses En règle générale, une interface est équivalente à une classe
opérations, elle est dite classe cliente de l’interface abstraite qui n’a que des méthodes abstraites et éventuellement des
champs static final .
• On utilise une relation de dépendance entre la classe cliente et
l’interface requise Depuis Java 8 les interfaces peuvent avoir des méthodes par défaut :
«interface» public interface MonInterface {
Voiture ClasseCliente
public void faireQuelqueChose();
+demarrer(): void public class ClasseDeTest {
utilise +traitement(v: Voiture): void
+arreter(): void default public void faireAutreChose(){ public static void main(String[] args) {
System.out.println("Implémentation par défaut"); MonInterface obj = new MaClasse();
} obj.faireQuelqueChose(); // classique
} obj.faireAutreChose(); // default
}
void traitement (Voiture v){ }
Berline Camion ... public class MaClasse implements MonInterface {
v.demarrer() public void faireQuelqueChose(){
+demarrer(): void +demarrer(): void ... System.out.println("Mon implémentation");
+arreter(): void +arreter(): void v.arreter() }
... } Héritage de comportement
}
Code Java
Important
• Toute classe implémentant l’interface pourra être utilisée - Les méthodes par défaut ne peuvent pas manipuler des attributs
principe de substitution dynamiques !
29 30
Java il peut être implicite ou explicite. public void dormir(){ public void aboyer(){ public void miauler(){
// du code ici // du code ici // du code ici
} } }
Cast implicite : } } }
En l’utilisant avec attention, parfois le transtypage peut être utile... Voyez-vous un danger avec le cast explicite ?
31 32
Héritage et casting : bêtisier Héritage : bilan
public class Animal{ public class Chien extends Animal{ public class Chat extends Animal{ extends ≈ héritage de type, de comportement et d’état
private String nom;
public Animal(String nom){ public Chien(String nom){ public Chat(String nom){ implements ≈ héritage de type (+ comportement en Java 8)
this.nom = nom; super(nom) super(nom)
} } }
• l’héritage est très pratique et souvent naturel...
public void dormir(){ public void aboyer(){ public void miauler(){
// du code ici // du code ici // du code ici
} } } • ...mais attention au respect du contrat des classes-parentes
} } }
Conseils :
Animal a = new Chien ("Bernny"); // OK ou pas ? • si possible préférez les interfaces plutôt que les classes-mères
Chien c = (Chien) a; // OK ou pas ?
c.aboyer(); //OK ou pas? concrètes −→ alternative plus souple
((Chien)a).aboyer(); //OK ou pas?
Animal b = new Chat ("Tom"); //OK ou pas?
Chien c1 = (Chien) b; //OK ou pas ?
• si votre seul but c’est réutiliser du code, dites-vous bien
c1.aboyer(); //OK ou pas ?