Guillaume Bouyer
ENSIIE
|
Connaître les concepts clés et maitriser le vocabulaire de la programmation orientée objet
Savoir mener une conception logicielle avec méthode
Appliquer ces connaissances dans un petit projet de gestion dans le langage Java
Guillaume Bouyer
Vitera Y
Séance | Date | Description |
---|---|---|
1 | 29 mars | Cours POO/Java |
2 | 4 avril | TP 1 |
3 | 12 avril | TP 1 |
4 | 18 avril | Cours Exceptions + TP 2 |
vacances | ||
5 | 3 mai | Cours Generics/Collections + Interro + TP 3 |
6 | 10 mai | Cours UML/Conception + TP 3 + Sujet projet |
7 | 16 mai | Cours Design patterns + Projet |
8 | 24 mai | Projet |
pont | ||
9 | 7 juin | Projet + Interro |
10 | 14 juin | Projet |
11 | 21 juin | Projet |
12 | 27 juin | Soutenances |
2 Interros
Projet
Un programme doit réunir un ensemble de qualités vis-à-vis de ses utilisateurs et de ses développeurs
=> Compromis
Il existe des concepts, des méthodes et des outils pour aider à atteindre ces critères de qualité
La programmation orientée objet rassemble plusieurs de ces moyens
“Une abstraction désigne les caractéristiques essentielles d'un objet qui le distingue de tous les autres types d'objets et fournit ainsi des limites conceptuelles nettement définies par rapport à la perspective du spectateur.” (Grady Booch)
Ces caractéristiques sont relatives au contexte dans lequel l'entité est utilisée
Regrouper au sein d'une même entité des données et les méthodes qui les manipulent
Décomposer un problème (programme) afin de réduire sa complexité globale, en modules connectés entre eux mais aussi indépendants que possible
Permet entre autres de :
Compile once, execute anywhere
Un programme Java est compilé en bytecode
javac Programme.java
le bytecode Programme.class
est interprétable par une machine virtuelle qui est installable sur toutes les plateformes
java Programme
Langage fortement orienté objet
Langage typé
Syntaxe C-like
Pas de pointeurs (mais des références)
Eclipse ou autre IDE
En POO, les objets sont la base de l'abstraction, l'encapsulation et la modularité.
Un “objet” sert à modéliser les entités manipulées par le programme
Un objet possède un état et un comportement, caractérisé par des réactions à des messages qu'on peut lui envoyer
Un objet possède :
les valeurs des attributs forment l'état interne de l'objet à un instant donné
données : solde, client…
opérations : débiter, créditer, virer…
données : dimensions, position sur l'écran, présence ou non d'une barre d'ascenseur, couleur de fond…
opérations : fermer, réduire, modifier la taille, déplacer…
données : âge, taille, poids…
opérations : peser, mesurer, passer un examen…
Rq : Certains attributs font référence à d'autres objets
“Que doit faire mon programme ?”
L'approche procédurale nécessite de
Le programme principal enchaine les traitements sur les données
“De quoi doit être composé mon programme ?”
L'approche objet nécessite de
Le programme principal :
Exemples : livre, journal, employé, lecteur
Une classe :
définit un type
est identifiée par un nom
est composée de membres qui peuvent être
Notation UML (Unified Modeling Language)
Dans un fichier Compte.java
(NB : nom identique à celui de la classe)
class Compte {
//attributs (fields)
double solde;
Client client;
//méthodes
void debiter (double n) { solde = solde - n; }
void crediter (double n) { solde = solde + n; }
double consulter () { return solde; }
void afficher () { System.out.println(solde); }
}
Dans un fichier Compte.h
class Compte {
double solde;
Client client;
void debiter (double n);
void crediter (double n);
double consulter ();
void afficher ();
}
Dans un fichier Compte.cpp
#include "Compte.h"
void Compte::debiter (double n) {solde = solde - n;}
void Compte::crediter (double n) {solde = solde + n;}
double Compte::consulter () {return solde;}
void afficher () {out << solde << endl;}
Les objets utilisés dans les programmes sont des représentations dynamiques créées à partir du modèle donné par la classe. Chaque instance de la classe :
Une classe peut être instanciée plusieurs fois
Les références permettent d'identifier les objets
Une référence contient l'adresse d'un objet en mémoire = permet d'accéder à la structure de données correspondante aux attributs propres à l’objet
// déclaration de 3 références dans une méthode
Banque bnp; // identificateur de classe (= type)
Compte martin;
Compte dupond;
Rappel : il n'y a pas de pointeurs en Java
Déclarer une référence ne suffit pas à créer un objet, c'est seulement un nom pour accéder à un objet
Mot clé new
puis appel à une méthode particulière qui porte le nom de la classe : le constructeur
new
renvoie une référence vers l'instance créée, qui peut être affectée à une variable X
du type correspondant
// dans une méthode d'une classe
Compte martin; // déclaration de deux références
Compte dupond; // NB aucun objet n'est créé
martin = new Compte(); // création de 2 instances avec constructeur
dupond = new Compte(); // affectation aux références
Rq : l'objet créé contient les valeurs des attributs, mais pas le code des méthodes
Il peut y en avoir plusieurs (doivent se distinguer par le nombre et le type des arguments)
=> surcharge des constructeurs (comme pour les méthodes)Il existe un constructeur par défaut (utilisé dans les exemples précédents)
Compte(){ // Remplace le constructeur par défaut
solde = 0.0;
client = new Client(); // Appel au constructeur par défaut de Client
}
public Compte(Client c){ // Constructeur valué à 1 argument
solde = 0.0;
client = c;
}
Compte(double s, String nomClient){ // Constructeur valué à 2 arguments
solde = s;
client = new Client(nomClient); // Suppose qu'il existe le constructeur correspondant
// dans la classe Client
}
Compte(Compte c){ // Constructeur de copie
solde = c.solde;
client = c.client;
}
Compte dupont = new Compte();
Compte martin = new Compte(100.0, "Martin");
Compte durand = new Compte(martin);
Notions de base
Manipuler et modifier l'état d'un objet se fait en lui appliquant ses méthodes (celles définies dans sa classe) avec l'opérateur “point” : .
objet.methode(arg1, arg2, ... )
NB : l'objet qui possède la méthode n'est pas pris en argument, contrairement au C par ex.
NB : si l'objet n'est pas correctement initialisé on ne peut appeler ses méthodes (erreur de compilation ou d'exécution)
Rq : certains auteurs considèrent que les objets interagissent et communiquent entre eux par l'envoi de messages, l'objet étant le “receveur” et la méthode le “sélecteur”
Compte martin = new Compte();
Compte dupond = new Compte();
martin.afficher(); // 0
dupont.crediter(10);
dupont.afficher(); // 10
martin.afficher(); // 0
x = dupont.consulter(); // x = 10
Compte.debiter(5); // Erreur de compilation : Compte n'est pas un objet
La surcharge d’une méthode (ou d’un constructeur) permet de définir plusieurs fois une même méthode/constructeur avec des arguments différents.
Le compilateur choisit la méthode qui doit être appelée en fonction du nombre et du type des arguments
NB : le type de retour ne compte pas, 2 méthodes ne peuvent pas se différencier uniquement avec lui
class Compte {
...
void crediter (double n) { solde = solde + n; }
void crediter (float f) { ... }
void afficher () { System.out.println(solde); }
void afficher (String format) { ... }
}
La règle générale de Java est que les arguments sont passés par valeur : l’appel de méthode se fait par copie des valeurs passées en argument.
Mais à cause des références ce n'est pas si simple…
static
Membres qui ne dépendent pas d'une instance de la classe
Un attribut statique doit être accédé par des méthodes statiques
Ex. valeur de précision commune à tous les Point2D
public static double epsilon = 1E-5;
Les membres de classe sont accédées au travers du nom de la classe
NomDeClasse.nomDeVariable
NB : à l'intérieur du corps d'une méthode statique il n'est possible d'accéder qu'aux membres statiques de la classe (this
n'existe pas)
Ex.
System.out.println("Hello World");
//méthode "println"" de l'attribut static "out"" de la classe "System""
S'il y a besoin d'une initialisation autre qu'une valeur, c'est impossible dans un constructeur, puisque le membre doit exister indépendamment des objets
=> Bloc spécifique qui sera exécuté lors de la création de la classe
public class C {
static int T[];
static {
T = new int[10];
for (int i = 0 ; i<10 ; i++)
T[i]=i;
}
}
final
Indique que l'élément considéré ne pourra pas être modifié : on ne pourra lui donner une valeur une seule fois dans le programme
final
: variablePour une variable locale : sa valeur ne pourra être donnée qu'une seule fois
final Employe e = new Employe("Bibi");
e.nom = "Toto"; // Autorisé
e.setSalaire(12000); // Autorisé
e = new Employe("Bob"); // INTERDIT
final
: variable de classePour une variable de classe : constante dans tout le programme
static
static final double PI = 3.14;
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello World !");
}
}
Quelle sont les propriétés du main
?
Attention aux conséquences
Compte dupont = new Compte();
Compte martin = new Compte();
Compte titi;
martin.afficher();
dupont.crediter(10);
dupont.afficher();
titi = dupont;
titi.afficher();
titi.debiter(5);
titi.afficher();
dupont.afficher();
dupont.crediter(100);
titi.afficher();
Quelles sont les valeurs imprimées ?
null
null
signifie que la référence ne pointe vers aucune instance
L'utilisation d'une méthode (ou d'une donnée) à partir d'une variable de type référence à null
provoque une erreur à l'exécution
Compte c = null; // équivalent à Compte c;
c.crediter(5);
=> Exception in thread main java.lang.NullPointerException at
ProgrammePrincipal.main(ProgrammePrincipal.java:4)
Cause classique de bugs
Une référence particulière est celle à l'objet courant : mot clé this
Permet d'utiliser les attributs et les autres méthodes de l'objet “courant” dans une méthode
Permet de lever les ambiguités : quand un nom d'attribut est utilisé dans le corps d’une méthode, c'est implicitement celui de l'objet courant
Permet aussi à un objet de se passer lui-même en paramètre d'une méthode :
sender.message(this, content)
class Compte {
double solde = 0.0;
Client client;
public Compte(Client client){ // Constructeur valué à 1 argument
solde = 0.0;
this.client = client; // this *obligatoire* ici
}
public Compte(){
this(new Client()); //appel au constructeur précédent
}
//equivalent à : public Compte(){ solde = 0; client = new Client(); }
void afficher () { System.out.println(solde); }
void debiter (double n) {
this.solde = this.solde - n; // this optionnel ici
}
void modifierSolde (double solde) {
this.solde = solde; // this *obligatoire* ici
this.afficher(); // this optionnel ici
}
}
class Ecole {
Compte compte;
//...
}
Compte bnp = new Compte();
Compte bnp2 = bnp ;
Ecole ensiie;
ensiie = new Ecole();
ensiie.compte = bnp;
Comparer 2 références (avec ==
) revient à comparer 2 adresses
class ProgPrincipal {
public static void main (String arg[]){
Compte c = new Compte();
Compte c2 = c;
Compte c3;
if (c==c2) System.out.println("c=c2");
else System.out.println("c!=c2");
c3 = new Compte;
if (c==c3) System.out.println("c=c3");
else System.out.println("C!=c3");
}
}
Certains langages ont des fonctionnalités de destruction des instances (ex. C++
)
En Java, la gestion mémoire est assurée automatiquement par le Garbage Collector (“ramasse-miettes”)
Lorsqu'une instance n'est plus référencée, c'est-à-dire plus accessible à partir des variables du programme, la mémoire qu'elle occupait est libérée
Compte dupont = new Compte();
Compte martin = new Compte();
Compte durant = new Compte();
...
dupont = null; // l'instance précédemment référencée par dupont pourra étre détruite
durant = martin; // l'instance précédemment référencée par durant pourra étre détruite
NB : destruction asynchrone (thread) donc temporalité non garantie
public class Cercle {
private Point centre; //Cercle "possède" un centre
private double r;
public void translater(double dx, double dy) {
centre.translater(dx,dy);
}
}
L'attribut a une existence autonome (“Has-a relationship”)
Il peut être partagé (à un même moment il peut être lié à plusieurs instances d'objets)
Il peut être utilisé en dehors de la classe (NB : attention aux effets de bord)
public class Cercle {
...
public Cercle( Point centre, double r) {
this.centre = centre;
this.r = r;
}
...
}
L'attribut n'est pas partagé (“Contains relationship”")
Les cycles de vie de l'objet et de son attribut sont liés
public class Cercle {
...
public Cercle( Point centre, double r) {
this.centre = new Point(centre);
this.r = r;
}
...
}
Caractéristique fondamentale des langages objets (avec l'encapsulation)
Permet de construire une classe à partir d'une classe existante pour la rendre plus spécifique, plus spécialisée ou étendre ses fonctionnalités
Une classe générale définit un ensemble d’attributs et/ou méthodes qui sont partagés par d’autres classes, qui “héritent de” (ou spécialisent) cette classe générale
Cette relation d'héritage entre classes définit une hiérarchie
En Java la classe Object
est la racine de l'arbre : toute classe hérite implicitement de cette classe existante
L'héritage dans les langages objets suit la règle suivante (polymorphisme d'inclusion) :
Si C' est une sous-classe de C et o' un objet instance de C' alors o' a aussi le type C
Cette règle suffit pour pouvoir utiliser une méthode qui attend des objets instances de la classe C avec des objets instances de la classe C'
=> Un code écrit pour des classes parentes peut être utilisé avec les classes filles
boolean estCrediteur (Compte x)
est utilisable sur les CompteEpargne
et les CompteSecurise
Chaque classe héritière :
class Station {
String nom;
int nbHotel;
int categorie;
...
}
class StationHiver extends Station {
int nbRemontee;
int nbPiste;
Date ouverture;
...
}
class StationBalneaire extends Station {
int nbPlage;
Date ouverture;
Color drapeau;
...
}
class Personne {
...
void setDomicile (String adresse) { ...}
}
class Employe extends Personne {
void setBureau (String adresse) { ...}
double calculerSalaire (...) { ... }
}
class Station {
boolean reserverSejour(int n, Date d){ ... }
...
}
class StationHiver extends Station {
boolean reserverSejour(int n, Date d){ ... } // redéfinie pour inclure la location
// de skis et l'achat du forfait
...
}
taux
en % et une méthode appliquerInterets()
qui permet d'augmenter le solde selon ce taux
L‘appel à une méthode déclenche un mécanisme de recherche dans le graphe d'héritage jusqu’à trouver une méthode ayant un nom et une signature correspondant à l'appel
Surcharge = résolu à la compilation
Redéfinition (la méthode a le même profil dans la classe fille)
Appel explicite de la méthode de la classe mère : super.methode()
class CompteSecurise extends Compte{
void debiter(double d, boolean depassement){
if (solde - d < 0)
if (depassement)
solde = solde - d ;
else solde = solde - d;
}
double consulter(){
afficher();
return solde;
}
}
CompteSecurise dupont = new CompteSecurise();
dupont.debiter(10,false);
dupont.afficher();
dupont.debiter(5);
dupont.afficher();
débiter
et consulter
sont redéfinies ou surchargées ?
on hérite d'une seule classe, chaque classe a un seul père dans le graphe d'héritage (hiérarchie = arbre)
Ex : Java
on hérite de plusieurs classes simultanément (hiérarchie = graphe orienté sans circuit)
Ex : C++, OCaml, Eiffel
Chaque fois qu‘un objet est créé les constructeurs sont invoqués en remontant en séquence de classe en classe dans la hiérarchie jusqu’à la classe Object
Object
est toujours exécuté en premier, suivi des constructeurs des différentes classes en redescendant dans la hiérarchie
Appel explicite des constructeurs de la classe mère : super(paramètres constructeur)
this()
public class PointCouleur extends Point {
Color c;
public PointCouleur(double x, double y, Color c){
super(x,y);
this.c = c;
}
}
Rq : S'il n'y a pas d'appel explicite, appel implicite à super()
mais ce constructeur par défaut de la classe mère peut ne plus exister…
Méthode “abstraite” ou “virtuelle” = méthode dont il manque le corps (seul son type et ses paramètres sont donnés)
Classe abstraite = classe qui possède une ou plusieurs méthodes abstraites (ou qui est définie explicitement comme telle)
Une classe abstraite ne peut pas être instanciée
Une classe abstraite peut être héritée
Une méthode abstraite peut être utilisée dans une méthode concrète
En Java, mot clé abstract
Une sous-classe peut concrétiser tout ou partie des méthodes abstraites. S'il en reste elle sera elle-même abstraite.
abstract class Figure {
abstract double aire();
}
class Carre extends Figure{
double cote;
double aire (){
return cote*cote;
}
}
class Cercle extends Figure{
double rayon;
private double pi = 3.14;
double aire (){
return pi*rayon*rayon;
}
}
Rq : héritage ici = spécialisation, raffinement
abstract class Aliment{
String nom;
abstract String modeDeConsommation();
void info(){
System.out.println(this.modeDeConsommation());
}
}
class Pain extends Aliment{
String modeDeConsommation(){
return "cuit";
}
}
class FoieGras extends Aliment{
String modeDeConsommation(){
return "cru ou cuit, avec un verre de Sauternes";
}
}
Une interface est comparable à une classe abstraite dont toutes les méthodes seraient abstraites
Une interface est un contrat entre une classe qui implémente l'interface et une classe qui utilise l'interface
En Java mot-clé interface
Ex : l'interface Runnable
implémentée par les threads
public interface Runnable {
public void run();
}
Rq : Depuis Java 8 il est possible d'implémenter des méthodes dans une interface (avec le mot-clé default
) ce qui rend la différence moins claire avec les classes abstraites
Fonctionne un peu à la manière d'un héritage avec mot-clé implements
Ex : la classe Thread
qui implémente Runnable
public class Thread implements Runnable {
public Thread() target= null; ...
public Thread(Runnable t) target = t ; ...
...
public void run() {
if (target != null) target.run();
}
...
private Runnable target;
...
}
L'héritage multiple n'existe pas en Java mais les interfaces sont un moyen de s'en approcher
Consiste à protéger les données d'un objet de modifications extérieures (autres classes).
Une classe décrit
Conséquences
=> Robustesse du code
=> Facilite l'évolution du logiciel
Outil pour l'encapsulation
Plusieurs niveaux de visibilité peuvent être définis pour les classes, les attributs et les méthodes, en général 3 (dépendant du langage) :
public
public class MaClasse {
public int monEntier;
public void afficher() { ... }
}
private
public class MaClasse {
private int monEntier;
public int getEntier() { return monEntier; }
private int calculInterne(int i) { ... }
}
Les classes peuvent être organisées en répertoires particuliers, les packages (voir TP)
package Comptes;
public class Compte{
...
}
Sans instruction explicite, le package sera default
protected
En cas d'absence de modificateur
protected
(peut être utilisée dans une autre classe du même package)
Donc, pas d'encapsulation de données par défaut => DANGER !
public class Compte {
????? double solde;
????? Client client;
public Compte(Client client){
solde = 0.0;
this.client = client;
}
public void debiter (double n) { solde = solde - n; }
public void crediter (double n) { solde = solde + n; }
public double consulter () { return solde; }
public void afficher () { System.out.println(solde); }
private double calculInterne() {...}
}
public int solde
titi.solde = -100
est possible, même si un compte sécurisé devrait toujours avoir un solde positif ou nul
private int solde
solde
n'est pas visible dans CompteEpargne
et CompteSecurisé
: il faut utiliser la méthode consulter
pour y accéder et titi.solde = -100
est interdit
protected int solde
solde
est visible dans CompteEpargne
et CompteSecurisé
mais pas à l'extérieur
Attributs private
ou protected
(si les sous-classes doivent les utiliser directement)
Pour chaque attribut, définir les méthodes d'accès public
(les “accesseurs”)
getX
, getY
pour Point
, consulter
pour Compte
setX
, setY
pour Point
, débiter
et créditer
pour Compte
Limiter au strict nécessaire les autres méthodes publiques
Les méthodes redéfinies dans les classes filles doivent fournir au moins les mêmes droits d'accès que dans la classe mère
Classes déclarées à l'intérieur d'une classe
private, public, protected
ou par défaut “package private”
abstract
ou final
On distingue
static
, même privés
class EnclosingClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
La classe englobante fournit un espace de noms pour les classes internes : son nom est de la forme EnclosingClass.InnerClass
(Sous-)classe déclarée dans un bloc (ex. une méthode)
class Hello {
public void read() {
System.out.println("Hello!");
}
}
class Website {
Hello helloInstance = new Hello() {
public void read() {
System.out.println("Bonjour");
}
}; //obligatoire !
}
Utile si
Ressources et conseils
Cours en ligne