COURS POO Vers 2020
COURS POO Vers 2020
COURS POO Vers 2020
Support de cours
Cours destiné aux Ingénieurs en SI
Donacien GUIFO KOUAM
[email protected]
Pré-requis :
Savoir écrire des algorithmiques, connaitre les principales structures de données, avoir effectué des travaux
pratiques en algorithmique et structure de donnée (aussi bien en programmation impérative que
fonctionnelle).
Formule pédagogique :
Exposé informel
Salle machine
Moyens pédagogiques :
Tableau
Support de cours
Méthodologie :
Cours intégré
Travaux dirigés (réalisation et correction d’exercices)
Travaux pratiques (JAVA sous Eclipse ou NetBeans)
Volume Horaire :
20 heures de cours
30 heures de travaux pratiques/dirigés
Objectifs généraux :
Comprendre les origines et l’intérêt d’un nouveau style de programmation
Maîtriser les concepts orientés objet en termes de définitions, syntaxe Java et usage
Savoir utiliser cette nouvelle approche pour modéliser des problèmes
Faire la liaison entre les différents éléments du cours (classes, objets, encapsulation, héritage,
polymorphisme, classe abstraite et interfaces)
Bibliographie :
http://java.com
www.openclassrooms.com
Cours Philippe Genoud, Java un langage Orienté Objets. L'approche Objets, Université
Joseph Fourrier, IMAG, Sept 2008
Bailly C, Chaline J.F., Ferry H.C & al, Les langages orientés objets, Cepadues éditions.
La programmation classique telle qu’étudiée au travers des langages C, Pascal… définie un programme comme
étant un ensemble de données sur lesquelles agissent des procédures et des fonctions. Les données constituent
la partie passive du programme. Les procédures et les fonctions constituent la partie active. Programmer dans
ce cas revenait à : - définir un certain nombre de variables (structures, tableaux…) - écrire des procédures
pour les manipuler sans associer explicitement les unes aux autres. Exécuter un programme se réduit alors à
appeler ces procédures dans un ordre décrit par le séquençage des instructions et en leur fournissant les
données nécessaires à l’accomplissement de leurs tâches.
Données
Fonction1
Fonction2
Procédure1
Procédure2
Dans cette approche données et procédure sont traitées indépendamment les unes des autres sans tenir
compte des relations étroites qui les unissent. Les questions qu’on peut se poser dans ce cas :
1. Cette séparation (données, procédures) est-elle utile ?
2. Pourquoi privilégier les procédures sur les données (Que veut-on faire ?) ?
3. Pourquoi ne pas considérer que les programmes sont avant tout des ensembles d’objets informatiques
caractérisés par les opérations qu’ils connaissent ?
Les langages objets sont nés pour répondre à ces questions. Ils sont fondés sur la connaissance d’une seule
catégorie d’entités informatiques : les objets. Un objet incorpore des aspects statiques et dynamiques au sein
d’une même notion. Avec les objets se sont les données qui deviennent prépondérantes.
On répond tout d’abord à la question « De quoi parle-t-on ? »
Un objet réagit à certains messages qu’on lui envoie de l’extérieur; la façon et la façon dont il réagit détermine
le comportement de l’objet.
Un objet ne réagit pas toujours de la même façon à un même message; sa réaction dépend de l’état dans
lequel il se trouve.
Un objet possède :
– Une identité unique (permet de distinguer un objet d’un autre)
– Un état interne donné par des valeurs de variables (ou attributs)
• Attributs décrivent l’état de l’objet à un instant donné
ex: patient mesure 1,82 m et pèse 75 Kg
IAI – 2018/2019 Donacien GUIFO Page 7
• Attributs sont typés et nommés
ex: float hauteur; float poids;
– Un comportement (capacités d’action de l’objet) donné par des fonctions ou sous-programmes, appelés
méthodes (ou opérations).
• Les méthodes définissent le comportement de l’objet (ce qu’il peut faire, comment il peut le faire…)
et ses réactions aux stimulations externes
ex: un étudiant passe un examen, etc…
• Les méthodes implémentent également les algorithmes invocables sur cet objet
Exemple :
Comportement de l’objet
Etat de l’objet
(caché)
(public)
Solutions proposées :
Découpler (séparer) les parties des projets ;
Limiter (et localiser) les modifications lors des évolutions ;
Réutiliser facilement du code.
Chaque langage de programmation appartient à une “famille” de langages définissant une approche ou une
méthodologie générale de programmation. Par exemple, le langage C est un langage de programmation
procédurale car il suppose que le programmeur s’intéresse en priorité aux traitements que son programme
devra effectuer. Un programmeur C commencera par identifier ces traitements pour écrire les fonctions qui
les réalisent sur des données prises comme paramètres d’entrée.
La programmation orientée-objet (introduite par le langage SmallTalk) propose une méthodologie centrée sur
les données. Le programmeur Java va d’abord identifier un ensemble d’objets, tel que chaque objet représente
un élément qui doit être utilisé ou manipulé par le programme, sous la forme d’ensembles de données. Ce
n’est que dans un deuxième temps, que le programmeur va écrire les traitements, en associant chaque
traitement à un objet donné. Un objet peut être vu comme une entité regroupant un ensemble de données et
de méthodes (l’équivalent d’une fonction en C) de traitement.
IAI – 2018/2019 Donacien GUIFO Page 8
Il existe plusieurs langages orienté objet. Les principaux sont :
C++ : très utilisé
C# : langage de Microsoft (appartient à .NET)
Objective C : langage utilisé par Apple
PHP : langage très utilisé en programmation Web
Python de plus en plus utilisés en ingénierie des données (Data science)
Ruby
Eiffel
Ada
Smalltalk
Etc…
La syntaxe change mais le concept objet est le même quel que soit le langage utilisé !
B
Dans le cadre de ce cours, nous allons utiliser le langage JAVA qui :
est un langage de programmation orienté objet
crée par James Gosling et Patrick Naughton (Sun Microsystems)
présenté officiellement le 23 mai 1995.
class Rectangle {
int longueur ;
int largeur ;
int origine_x ;
int origine_y ;
void deplace(int x, int y) {
this.origine_x = this.origine_x + x ;
this.origine_y = this.origine_y + y ;
}
int surface() {
return this.longueur * this.largeur ;
}
}
Pour écrire un programme avec un langage orienté-objet, le programmeur écrit uniquement des classes
correspondant aux objets de son système. Les traitements à effectuer sont programmés dans les méthodes de
ces classes qui peuvent faire appel à des méthodes d’autres classes. En général, on définit une classe, dite
“exécutable”, dont une méthode peut être appelée pour exécuter le programme.
Objectifs spécifiques
Introduire la notion d’encapsulation et ses intérêts pratiques
Comprendre les droits d’accès aux membres et aux classes
Maîtriser le concept de surcharge des méthodes
Eléments de contenu
1. L’encapsulation
2. Modificateurs de visibilité et accès
3. Surcharge des méthodes
L’encapsulation est la possibilité de ne montrer de l’objet que ce qui est nécessaire à son utilisation.
L’encapsulation permet d’offrir aux utilisateurs d’une classe la liste des méthodes et éventuellement des
attributs utilisables depuis l’extérieur. Cette liste de services exportables est appelée l’interface de la classe et
elle est composée d’un ensemble des méthodes et d’attributs dits publics (Public).
Les méthodes et attributs réservés à l’implémentation des comportements internes à l’objet sont dits privés
(Private). Leur utilisation est exclusivement réservée aux méthodes définies dans la classe courante.
Les avantages de l’encapsulation sont :
Simplification de l’utilisation des objets,
Meilleure robustesse du programme,
Simplification de la maintenance globale de l’application
Les attributs et les méthodes sont précédés lors de la déclaration par l’un des modificateurs de visibilité
suivants : «public», «private», «protected» et Néant.
Une méthode, classe ou attribut sont déclarés comme publiques « public » s’ils doivent être visibles
à l’intérieur et à l’extérieur quel que soit leur package.
Une méthode, classe ou attribut ne sont pas précédés par un modificateur de visibilité explicite
(Néant) ne vont être visibles qu’à l’intérieur de même package. C'est-à-dire seules les classes de
même package peuvent accéder aux attributs et méthodes de classes « amies ». Ce modificateur de
visibilité est aussi appelé « modificateur de package » ou modificateur « freindly ».
Une méthode ou attributs définis comme étant privés « private » s’ils sont accessibles uniquement
par les méthodes de la classe en cours. Ils ne sont pas accessibles ailleurs.
Une méthode ou attribut sont définis comme protégés « protected » s’ils ne peuvent être
accessibles qu’à travers les classes dérivées ou les classes de même package.
Pour accéder aux attributs de l’intérieur de la classe, il suffit d’indiquer le nom de l’attribut que
l’on veut y accéder.
IAI – 2018/2019 Donacien GUIFO Page 13
Pour accéder de l’extérieur de la classe, on utilise la syntaxe suivante :
<nom_méthode>.<nom_attribut>
Exemple 1
Si longueur et largeur étaient des attributs publics de Rectangle, on peut écrire le code suivant dans la méthode
« main » de la classe Test_Rect
Si longueur et largeur étaient des attributs privés « private », les instructions suivantes seront refusées par le
compilateur
r.longueur = 20; //faux
r.largeur = 15; //faux
int la = r.longueur ; //faux
Il fallait dans le deuxième cas définir des méthodes d’accés « setlong (int) » et « setlarg (int) » qui permettent
de modifier les valeurs des attributs et les méthodes d’accès « getlong ()» et « getlarg ()» pour retourner les
valeurs de longueur et de largeur d’un objet Rectangle. Dans ce cas, les instructions seront les suivantes :
r.setlong(20); //juste
r.setlarg(15); //juste
int la = r.getlong() ; //juste
Exemple 2
En fonction du type et du nombre de paramètres lors de l’appel, la méthode correspondante sera choisie.
Le mécanisme de surcharge permet de réutiliser le nom d’une méthode déjà définie, pour une autre méthode
qui en différera par ses paramètres.
Exemple
void allonger(int l) //Pour allonger la longueur d’un rectangle
{
Lors de l’appel des méthodes surchargées, le compilateur sait distinguer entre les méthodes en considérant le
nombre et les types des paramètres
Rectangle r = new Rectangle (5,10) ; //Appel de constructeur à 2 paramètres
r.allonger (10) ; // Appel de la 1ère méthode surchargée
r.allonger (10, 3) ; // Appel de la 2ème méthode surchargée
Exercice 1:
public class Livre {
// Variables
private String titre, auteur;
private int nbPages
// Constructeur
public Livre(String unAuteur, String unTitre) {
auteur = unAuteur;
titre = unTitre;}
// getter
public String getAuteur() {
return auteur;
}
// Setter
void setNbPages(int n) {
nbPages = nb;}
}
Exercice 2:
Accesseurs et modificateurs
1) Modifiez la classe Livre :
Ajoutez un gettet pour la variable titre et la variable nbPages.
Ajoutez un setter pour les variables auteur et titre.
Changez le setter de nbPages : il ne devra changer le nombre de pages que si on lui passe en paramètre
un nombre positif, et ne rien faire sinon, en affichant un message d'erreur.
2) Dans la méthode main(),
a- indiquez le nombre de pages de chacun des 2 livres,
b- faites afficher ces nombres de pages,
c- calculez le nombre de pages total de ces 2 livres et affichez-le.
Exercice 3:
1) Dans La classe Livre, ajoutez une méthode afficheToi() qui affiche une description du livre (auteur, titre et
nombre de pages).
2) Ajoutez une méthode toString() qui renvoie une chaîne de caractères qui décrit le livre. Modifiez la méthode
afficheToi() pour utiliser toString().
Voyez ce qui est affiché maintenant par l'instruction System.out.println(livre).
3) Ajoutez 2 constructeurs pour avoir 3 constructeurs dans la classe :
Exercice 4
1) Contrôle des variables private par les setters
a- Ajoutez un prix aux livres (nombre qui peut avoir 2 décimales ; type Java float ou double) avec 2
méthodes getPrix et setPrix pour obtenir le prix et le modifier.
b- Ajoutez au moins un constructeur qui prend le prix en paramètre.
c- Testez. Si le prix d'un livre n'a pas été fixé, la description du livre (toString()) devra indiquer "Prixpas
encore fixé".
d- On bloque complètement les prix : un prix ne peut être saisi qu'une seule fois et ne peut être modifié
ensuite (une tentative pour changer le prix ne fait qu'afficher un message d'erreur).
Réécrivez la méthode setPrix (et autre chose si besoin est). Vous ajouterez une variable booléenne
prixFixe (pour "prix fixé") pour savoir si le prix a déjà été fixé.
e- Réécrire la méthode main () et prévoir le deux cas ( prix non fixé ou bien prix fixé plusieurs fois )
afficher le résultat de l’exécution.
Objectifs spécifiques
Technique d’héritage : intérêt et notation
Maitriser les droits d’accès d’une classe dérivée aux membres de la classe de base
Comprendre la construction d’un objet dérivé
Maîtriser la notion de redéfinition
Découvrir le concept de dérivations multiples
Eléments de contenu :
1. Le principe de l’héritage
2. La syntaxe
3. L’accès aux variables et méthodes dérivées
4. Construction et initialisation des objets dérivés
5. Dérivation successives
6. Redéfinition et surcharge de méthodes
7. Mots clés final et super
8. Des conseils sur l’héritage
Une classe qui hérite d'une autre est une sous classe et celle dont elle hérite est une super classe. Une classe
peut avoir plusieurs sous classes.
Attention : Une classe ne peut avoir qu'une seule classe mère : il n'y a pas d'héritage multiple en java.
Object est la classe parente de toutes les classes en java. Toutes les variables et méthodes contenues
dans Object sont accessibles à partir de n'importe quelle classe car par héritage successif toutes les classes
héritent d'Object.
4.2 Syntaxe
class Fille extends Mere { ... }
On utilise le mot clé extends pour indiquer qu'une classe hérite d'une autre. En l'absence de ce mot réservé
associé à une classe, le compilateur considère la classe Object comme classe parent.
Exemple
//Classe de base
class graphique {
private int x, y;
graphique (int x, int y) {this.x = x ; this.y = y ;}
void affiche () {
System.out.println (`" Le centre de l’objet se trouve dans : " + x + " et " + y) ;}
double surface () {return (0) ;}
} // fin de la classe graphique
//Classe dérivée1
class Cercle extends graphique {
private double rayon =1 ;
void affiche () {
Sustem.out.println (« C’est un cercle de rayon » + rayon) ;
Super.affiche () ;}
double surface ()
{return (rayon * rayon* 3.14) ;}
}
//Classe dérivée2
class Rectangle extends graphique {
private int larg, longueur ;
Rectangle ( int x, int y, int l1, int l2){
super (x,y) ;
longueur = l1 ; larg = l2 ;}
double surface () {return (longueur*largeur) ;}
}// fin de la classe Rectangle
//Classe dérivée3
class carré extends graphique {
Interprétation
Dans cet exemple, la classe Cercle hérite de la classe graphique les attributs et les méthodes, redéfinit
les deux méthodes surface et affiche et ajoute l’attribut rayon.
La classe Rectangle redéfinit la méthode surface et ajoute les attributs longueur et larg
La classe carré ajoute l’attribut côté et redéfinit la méthode surface.
Remarques
Le concept d’héritage permet à la classe dérivée de :
Hériter les attributs et les méthodes de la classe de base.
Ajouter ses propres définitions des attributs et des méthodes.
Redéfinir et surcharger une ou plusieurs méthodes héritées.
Tout objet peut être vu comme instance de sa classe et de toutes les classes dérivées.
Toute classe en Java dérive de la classe Object.
Toute classe en Java peut hériter directement d’une seule classe de base. Il n’y a pas la notion d’héritage
multiple en Java, mais il peut être implémenté via d’autres moyens tels que les interfaces(Chapitre
suivant).
Pour invoquer une méthode d'une classe parent, il suffit d'indiquer la méthode préfixée par super. Pour
appeler le constructeur de la classe parent il suffit d'écrire super(paramètres) avec les paramètres adéquats.
Le lien entre une classe fille et une classe parent est géré par le langage : une évolution des règles de gestion
de la classe parent conduit à modifier automatiquement la classe fille dès que cette dernière est recompilée.
En java, il est obligatoire dans un constructeur d'une classe fille de faire appel explicitement ou implicitement
au constructeur de la classe mère.
Les variables et méthodes définies avec le modificateur d'accès public restent publiques à travers l'héritage et
toutes les autres classes.
Une variable d'instance définie avec le modificateur private est bien héritée mais elle n'est pas accessible
directement, elle peut l’être via les méthodes héritées.
Si l'on veut conserver pour une variable d'instance une protection semblable à celle assurée par le modificateur
private, il faut utiliser le modificateur protected. La variable ainsi définie sera héritée dans toutes les classes
descendantes qui pourront y accéder librement mais ne sera pas accessible hors de ces classes directement.
Exemple 2 : La classe dérivée et la classe de base ne sont pas dans le même package
package Formes_geo ;
class graphiques {
private x, y ;
public String couleur ;
void affiche () {
System.out.println (« Le centre de l’objet = ( » + x + « , » + y + « ) » ) ; }
double surface ( ) {return (0) ;} } //fin de la classe graphiques
package FormesCirc ;
class cercle extends graphiques{
double rayon ;
void changer_centre ( int x1, int y1, double r){
x = x1 ; y = y1 ; // faux car x et y sont déclarés private dans la classe de base
rayon =r ;}
public double surface ( ) {return (rayon * rayon* 3.14) ;}
public void affichec (){
affiche () ; // FAUX car le modificateur de visibilité de ce membre est freindly
System.out.println (« Le rayon = » + rayon + « La couleur = « + couleur ) ;}
}
Interprétation :
Dans le deuxième cas, la classe dérivée et la classe de base ne sont pas dans le même package, donc la classe
cercle a pu accéder aux membres (attributs et méthodes) publiques (l’attribut couleur), mais, elle n’a pas pu
accéder aux membres « freindly » c'est-à-dire qui n’ont pas un modificateur de visibilité (la méthode affiche) et
aux membres privés (x et y).
Nous avons déjà vu qu’il existe 3 types de modificateurs de visibilité publiques (« public »), privés
(private) et de paquetage ou freindly (sans mention).
Il existe encore un quatrième droit d’accès dit protégé (mot clé « protected »).
Remarque
Dans le cas général, une classe de base et une classe dérivée possèdent chacune au moins un constructeur. Le
constructeur de la classe dérivée complète la construction de l’objet dérivé, et ce, en appelant en premier lieu le
constructeur de la classe de base.
Exemple1 (cas1)
Class A {
// pas de constructeur
}
class B extends A {
public B(….){
super() ; //appel de constructeur par défaut de A
………
}
B b = new B(…); // Appel de constructeur1 de A
Exemple2 (cas2)
Class A {
public A() {….} //constructeur1
public A ( int n) {…..} //constructeur2
}
class B extends A {
// …..pas de constructeur
}
B b = new B(); // Appel de constructeur1 de A
Exemple3 (cas 2)
Class A {
IAI – 2018/2019 Donacien GUIFO Page 22
public A ( int n) {…..} //constructeur1
}
class B extends A {
// …..pas de constructeur
}
B b = new B(); /* On obtient une erreur de compilation car A ne dispose pas un constructeurs sans argument et possède
un autre constructeur*/
Exemple4 (cas1 et 2)
Class A {
//pas de constructeurs
}
class B extends A {
//pas de constructeurs
}
B b = new B (); // Appel de constructeur par défaut de B qui Appelle le constructeur par défaut de A.
Vehicule
Un véhicule de tourisme est un descendant de voiture qui est lui-même descendant de vehicule.
Un camion benne est un descendant de camion qui est lui-même descendant de vehicule.
Rappel : La notion de l’héritage multiple n’existe pas directement en Java, mais elle peut être implémentée
via la notion des interfaces
De même une classe dérivée peut fournir une nouvelle définition d’une méthode de même nom et aussi de
même signature et de même type de retour.
Remarques
La re-déclaration doit être strictement identique à celle de la super classe : même nom, même
paramètres et même type de retour.
Grâce au mot-clé super, la méthode redéfinie dans la sous-classe peut réutiliser du code écrit dans
la méthode de la super classe, qui n’est plus visible autrement.
Exemple
//Super-classe
class graphiques {
private x, y ;
public String couleur ;
public void affiche () {
System.out.println (« Le centre de l’objet = ( » + x + « , » + y + « ) » ) ; }
double surface ( ) {return (0) ;} } //fin de la classe graphiques
//classe dérivée
class cercle extends graphiques{
double rayon ;
public double surface ( ) {
return (rayon * rayon* 3.14) ;} //redéfinition de « surface »
public void affiche () {//redéfinition de la méthode « affiche »
super.affiche () ; // appel de affiche de la super-classe
System.out.println (« Le rayon = » + rayon + « La couleur = « + couleur ) ;}
}
A*
B C*
D* E F
Remarque : En Java, une méthode dérivée peut surcharger une méthode d’une classe ascendante
Exemple
Class A{
public void f (int n) {…..}
…
}
Class B extends A{
public void f (float n) {…..}
…
}
Dans le main()
Aa,Bb;
int n; float x;
a.f(n) // appelle f(int) de A
a.f(x) // Erreur, on a pas dans A f(float)
b.f(n) // appelle f(int) de A
b.f(x) // appelle f(float) de B
Dans le main()
Aa,Bb;
int n , float x, double y ;
a.f(n) ;//Appel de f(int) de A
a.f(x) ;//Appel de f(float) de A
a.f(y) ; //Erreur, il n’ y a pas f(double) dans A
b.f(n) ;//Appel de f(int) de B car f(int) est redéfinie dans B
a.f(x) ;//Appel de f(float) de A
a.f(y) ;//Appel de f(double) de B
Le modificateur « final »
Exemple
final class A {
//Méthodes et attributs
}
class B extends A //Erreur car la classe A est déclarée finale
{//....}
Cas 2 : Le modificateur « final » placé devant une méthode permet d’interdire sa redéfinition
Class A {
final Public void f(int x) {…..}
//…Autres méthodes et attributs
}
Class B {
//Autres méthodes et attributs
public void f (int x) {…} // Erreur car la méthode f est déclarée finale. Pas de redéfinition
}
Les classes et les méthodes peuvent être déclarées finales pour des raisons de sécurité pour garantir que
leur comportement ne soit modifié par une sous-classe.
Objectifs spécifiques
Comprendre Le concept de polymorphisme à travers des exemples
Comprendre la conversion des arguments en liaison avec le polymorphisme
Comprendre la conversion explicite des références en rapport avec le polymorphisme
Eléments de contenu
1. Concept de polymorphisme
2. Exemple et interprétation
3. Conversion des arguments effectifs
4. Les conversions explicites des références
Le mot. « Poly » signifie « plusieurs », comme dans polygone ou polytechnique, et « morphe » signifie « forme »
comme dans…amorphe ou zoomorphe.
Nous allons donc parler de choses ayant plusieurs formes. Ou, pour utiliser des termes informatiques, nous
allons créer du code fonctionnant de différentes manières selon le type qui l'utilise.
Exemple 1
//Classe de base
class graphique {
private int x, y;
public graphique ( int x, int y) { this.x = x ; this.y = y ;}
public void identifie () {
System.out.println (« Je suis une forme géometrique ») ;}
public void affiche () {
this.identifie() ;
System.out.println (« Le centre de l’objet se trouve dans : » + x
+ « et » + y) ;}
double surface () {return (0) ;}
} // fin de la classe graphique
//Classe dérivée1
class Cercle extends graphique {
private double rayon =1 ;
public Cercle ( int x, int y, double r) {
super(x,y) ;
rayon = r ;}
public void identifie () {
System.out.println (« Je suis un cercle ») ;}
double surface (){
return ( rayon * rayon* 3.14) ;}
}
//Classe dérivée2
IAI – 2018/2019 Donacien GUIFO Page 29
class Rectangle extends graphique {
private int larg, longueur ;
Rectangle ( int x, int y, int l1, int l2){
super (x,y) ;
longueur = l1 ; larg = l2 ;}
double surface () {return (longueur*largeur) ;}
public void identifie() {
System.out.println(« Je suis un rectangle ») ;}
}// fin de la classe Rectangle
//Classe de test
class test_poly {
public static void main (String [] args) {
graphique g = new graphique (3,7);
g.identifie ();
g= new Cercle (4,8,10) ;// compatibilité entre le type de la classe et de la classe dérivéé
g.identifie() ;
g= new Rectangle (7,9,10,3) ;// compatibilité entre le type de la classe et de la classe dérivéé
g.identifie () ;}
}
Résultat à l’exécution
Je suis une forme géométrique
Je suis un cercle
Je suis un rectangle
Interprétation
Le même identificateur « g » est initialisé dans la 1ère instruction avec une référence de type « graphique », puis
on a changé la référence de cette variable dans l’instruction3 en lui affectant une référence de type « Cercle »,
puis dans l’instruction 5, on a changé sa référence avec une référence de classe dérivée
« Rectangle ».
Ces affectations confirment la compatibilité entre un type d’une classe de base et la référence d’une classe
dérivée.
L’autre point qui semble plus important c’est la ligature dynamique des méthodes, dans le sens où le résultat
de la méthode « identifie » a changé dans chaque appel selon le type effectif de la variable « g ».
class test_poly1 {
void presenter(graphque g){
g.identifier() ;}
public static void main (String [] args) {
c= new Cercle (4,8,10) ;
presenter(c);}
}
Pourquoi ?
class test_poly2 {
public static void main (String [] args) {
graphique [] tab = new graphique [6];
tab[0] = new graphique (3,2);
IAI – 2018/2019 Donacien GUIFO Page 30
tab[1] = new Cercle (10,7,3) ;
tab [2] = new Rectangle (4,7,8,6) ;
tab [3] = new graphique (8,10);
tab[4] = new Cercle (8,5,3) ;
tab[5 = new Rectangle (10,17,3,8) ;
for (i=0 ; i <=5 ; i++) {tab[i].affiche();}
}
}
Résultat de l’exécution
Je suis une forme géométrique Le centre de l'objet se trouve dans : 3 et 2
Je suis un cercle Le centre de l'objet se trouve dans : 10 et 7
Je suis un rectangle Le centre de l'objet se trouve dans : 4 et 7
Je suis une forme géométrique Le centre de l'objet se trouve dans : 8 et 10
Je suis un cercle Le centre de l'objet se trouve dans : 8 et 5
Je suis un rectangle Le centre de l'objet se trouve dans : 10 et 17
Exemple 3
class test_poly3 {
public static void main (String [] args) {
String c ;
graphique d;
int c= Integer.parseInt(args [0]) ;
switch (c) {
case 0 : d=new Cercle (10,10,10) ; break;
case 1 : d=new Rectangle (10,10,10,10) ; break;
case 2 : d= new graphique (10,10); break;
default: d= new graphique (0,0);
}
d.identifie () ;
System.out.println (« Surface = » + d.surface()) ;}
}
Résultat de l’exécution :
Le message affiché est en fonction de l’argument introduit dans la commande d’exécution. Le gros avantage
du polymorphisme est de pouvoir référencer des objets sans connaître à la compilation véritablement leur
classe et de pouvoir par la suite à l’exécution lancer le code approprié à cet objet. La liaison entre l’identificateur
de la méthode polymorphe et son code est déterminée à l’exécution et non à la compilation, on parle alors de
liaison dynamique.
Remarque :
Le polymorphisme se base essentiellement sur le concept d’héritage et la redéfinition des méthodes. Toutefois,
si dans le même contexte d’héritage, on a à la fois une redéfinition et une surcharge de la même méthode, les
règles de polymorphisme deviennent de plus en plus compliquées.
La notion de polymorphisme peut être généralisée dans le cas de dérivations successives.
Exemple1
class A{
public void identifie(){
class Test{
static void f(A a ) {
a.identitie(); }
}
…..
A a = new A();
B b = new B ();
Test.f(a);// OK Appel usuel; il affiche “objet de type A”
Test.f(b) ;/*OK une référence à un objet de type B est compatible avec une référence à un objet de type A.
L’appel a.identifie affiche : « Objetde type A » */
Exemple2
Remarque :
Dans le cas où la méthode f est surdefinie, il y a encore des règles plus compliquées lors de la conversion
implicite des types de paramètres.
Exemple :
Class graphique {…….}
Class Cercle extends graphique{……..}
…..
graphique g, g1 ;
Cercle c, c1 ;
c = new graphique (….) ; //Erreur de compilation
g = new Cercle (…) //Juste
g1 = new graphique (….) ; //évident
c1= new Cercle(…) ; //évident
c1 = g1 //faux (Conversion implicite illégale)
g1= c1 ; //juste ; (conversion implicite légale)
Il faut réaliser une conversion explicite c1 = (Cercle) g1 ;
Objectifs spécifiques
1. Introduire la notion de classe abstraite, les règles de définition et l’intérêt pratique
2. Introduire le concept d’interfaces, leurs intérêts pratiques et leurs relations avec l’héritage
Eléments de contenu
1. Les classes abstraites
2. Les interfaces
Une classe abstraite est une classe qui ne permet pas d’instancier des objets, elle ne peut servir que de
classe de base pour une dérivation par héritage.
Dans une classe abstraite, on peut trouver classiquement des méthodes et des champs, dont héritera
toute classe dérivée et on peut trouver des méthodes dites « abstraites » qui fournissent uniquement
la signature et le type de retour.
Syntaxe
abstract class A{
public void f() {……} //f est définie dans A
public abstract void g (int n) ; //g est une méthode abstraite elle n’est pas définie dans A
}
}
Utilisation
A a ; //on peut déclarer une référence sur un objet de type A ou dérivé
a = new A (….) ;//Erreur pas d’instanciation d’objets d’une classe abstraite
Si on dérive une classe B de A qui définit les méthodes abstraites de A, alors on peut :
a = new B(…) ; //Juste car B n’est pas une classe abstraite
Dès qu’une classe abstraite comporte une ou plusieurs méthodes abstraites, elle est abstraite, et ce
même si l’on n’indique pas le mot clé « abstract » devant sa déclaration.
Une méthode abstraite doit être obligatoirement déclarée « public », ce qui est logique puisque sa
vacation est d’être redéfinie dans une classe dérivée.
Les noms d’arguments muets doivent figurer dans la définition d’une méthode abstraite
public abstract void g(int) ; //Erreur : nom argument fictif est obligatoire
Une classe dérivée d’une classe abstraite n’est pas obligée de redéfinir toutes les méthodes abstraites,
elle peut ne redéfinir aucune, mais elle reste abstraite tant qu’il y a encore des méthodes abstraites non
implémentées.
Une classe dérivée d’une classe non abstraite peut être déclarée abstraite.
Le recours aux classes abstraites facilite largement la conception orientée objet. On peut placer dans une classe
abstraite toutes les fonctionnalités dont on souhaite disposer pour les classes descendantes :
Soit sous la forme d’une implémentation complète de méthodes (non abstraites) et de champs (privés
ou non) lorsqu’ils sont communs à tous les descendants,
Soit sous forme d’interface de méthodes abstraites dont on est alors sûr qu’elles existeront dans toute
classe dérivée instanciable.
6.1.4 Exemple
//Classe dérivée1
class Cercle extends graphique {
private double rayon =1 ;
void affiche () {
System.out.println (« C’est un cercle de rayon » + rayon) ;
Super.affiche() ; }
double surface (){
return ( rayon * rayon* 3.14) ;}
}
//Classe dérivée2
class Rectangle extends graphique {
private int larg, longueur ;
Rectangle ( int x, int y, int l1, int l2){
super (x,y) ;
longueur = l1 ; larg = l2 ;}
double surface () {return (longueur*largeur) ;}
}// fin de la classe Rectangle
// Classe de test
class test_poly2 {
public static void main (String [] args) {
graphique [] tab = new graphique [6];
//tab[0] = new graphique (3,2); Erreur car une classe abstraite ne peutpas être instanciée
tab[0] = new Cercle (3,2,7);
tab[1] = new Cercle (10,7,3) ;
tab[2] = new Rectangle (4,7,8,6) ;
tab[3] = new Rectangle (8,10,12,10);
tab[4] = new Cercle (8,5,3) ;
tab[5] = new Rectangle (10,17,3,8) ;
for (int i=0 ; i <=5 ; i++) {tab[i].affiche();}
}
}
Remarque : Une classe abstraite peut ne comporter que des méthodes abstraites et aucun champ. Dans ce cas, on
peut utiliser le concept de l’interface.
Syntaxe
6.2.1.3 Exemple
interface Affichable{
void affiche() ;
}
class Entier implements Affichable{
private int val ;
public Entier (int n){
val = n ;}
public void affiche() {
System.out.println (« Je suis un entier de valeur » + val) ;}
}
class Flottant implements Affichable{
private float val ;
public Flottant (float n){
val = n ;}
public void affiche() {
System.out.println (« Je suis un flottant de valeur » + val) ; }
}
public class TestInterface {
public static void main (String [] args) {
Afficheable [] tab = new Afficheable [2];
tab[0] = new Entier (25);
tab [1] = new Flottant (1.25);
tab [0].affiche(); tab [1].affiche; }
}
Résultat de l’exécution
public interface I{
void f (int n) ; //public abstract facultatifs
void g () ; //public abstract facultatifs
static final int max = 100 ;
}
Les constantes déclarées sont toujours considérées comme « static » et « final »
Remarque : L’interface est totalement indépendante de l’héritage : Une classe dérivée peut implémenter une ou
plusieurs interfaces.
Remarque2 : On peut définir une interface comme une généralisation d’une autre. On utilise alors le mot clé
«extends »
Exemple
interface I1{
void f(int n) ;
static final int max = 100;
}
interface I2 extends I1{
void g (int n);
static final int min = 10;
}
interface I2{
void g (int n);
static final int min = 10;
void f(int n) ;
static final int max = 100;
}
=> Nous supposons que la méthode « g » est présente de 2 façons différentes dans I1 et I2
Exemple
interface I1{
void g(); } // g est une méthode abstraite de I1
interface I2{
int g(); } // g est aussi une méthode abstraite de I2
class A implements I1, I2 {/* Erreur car void g() et int g() ne peuvent pas coexister au sein de la même
classe*/