Mécanismes d'abstraction
Méthode et classe abstraite
Mot-clé abstract
- Une méthode déclarée abstract ne possède pas de corps:
abstract void methodeAbstraite();
- Une classe comportant une méthode abstraite doit être déclarée comme abstraite:
abstract class ClasseAbstraite { ... }
- Une classe abstraite ne peut pas être instanciée.
ClasseAbstraite unObjet; //compile
unObjet = new ClasseAbstraite(); //ne compile pas
A quoi ça sert ?
L'abstraction sert à regrouper sous une même étiquette
une catégorie d'objets, éventuellement de classes différentes,
qui ont un point commun. Si seul ce point commun compte
dans un programme, il est raisonnable que tous les objets
de cette catégorie puissent être utilisés quelle que soit leur
classe.
Par exemple, on peut raisonnablement dessiner une moto, une voiture,
un train, un avion quand on nous le demande. En revanche, si on
nous demande de dessiner un véhicule, qui est une catégorie plus
abstraite, on voudra plus de précisions. Cependant, on sait que
l'action de se déplacer est commune à tous les véhicules.
Ex.1. Integration/Point de variation (5 min)
- Téléchargez le fichier Integration.java
qui permet d'intégrer numériquement la fonction \(g : \mathbb{R} \rightarrow \mathbb{R}\)
telle que \(g(x) = x^2\).
System.out.println("sur ["+a+","+b+"] par pas de "+delta);
double sum = 0;
for (double x = a; x <= b; x += delta)
sum += x*x * delta;
return sum;
- Encapsulez l'évaluation de la fonction dans la méthode evaluer d'une classe FonctionCarre
que vous instanciez au début du calcul.
sum += g.evaluer(x) * delta;
Ex.2. Integration/Parametre (10 min)
- Dans le code client, on veut maintenant approcher l'intégration de n'importe
quel trinôme \(t : \mathbb{R} \rightarrow \mathbb{R}\) tel que
\(t(x) = ax^2 + bx + c\).
- Ecrivez une classe Trinome qui modélise la fonction t.
Ajoutez un paramètre formel de type Trinome à la méthode de calcul
et modifiez l'appel de la méthode en conséquence.
Ex.3. Integration/Abstraction (10 min)
- Dans le code client, on veut maintenant approcher l'intégration de n'importe
quelle fonction de \(\mathbb{R}\) dans \(\mathbb{R}\).
- Créez une classe abstraite FonctionDeRDansR possédant une méthode abstraite evaluer
qui donne \(f(x)\) à partir de \(x\).
- Modifiez le type du paramètre formel de Trinome à FonctionDeRDansR
dans la méthode calculer.
- Créez la classe Sinusoide qui modélise la fonction
\(s : \mathbb{R} \rightarrow \mathbb{R}\) telle que
\(s(x) = Acos(x) + Bsin(x)\).
Testez avec une instance de la classe Sinusoide.
Interfaces
Interface
Une interface est un ensemble de requêtes.
Toutes les instances des classes implémentant une même interface
répondent (à leur manière) à toutes ces requêtes et sont donc
de ce point de vue interchangeables.
Il existe des appareils très différents fournissant un signal audio/vidéo
(lecteur Blu-ray, ordinateur, console de jeu). Vous pouvez pourtant tous
les relier à votre téléviseur par un connecteur approprié (HDMI) pourvu
qu'ils respectent tous la même interface (norme et prise).
Syntaxe
Une interface I liste toutes les requêtes qu'on peut adresser aux
instances des classes l'implémentant:
interface I {
void unePremiereRequete();
...
}
Une classe implémentant I est déclarée ainsi:
class A implements I { ... }
NB. Une classe peut dériver d'une autre et implémenter plusieurs interfaces:
class B extends A implements J, K { ... }
Polymorphisme
Interfaces et classes (abstraites) partagent le mécanisme de polymorphisme;
des objets de classes différentes sont interchangeables à partir du moment
où leurs classes héritent d'une même classe parente ou implémentent la même
interface.
abstract class A { ... }
class B extends A { ... }
interface I { ... }
class C implements I { ... }
A objetA = new B(); //transtypage ascendant implicite
I objetI = new C(); //idem
Classe abstraite vs interface
- Une classe purement abstraite, sans champs et dont toutes les méthodes sont abstraites,
ressemble à une interface.
- La différence est subtile:
- on préferera une interface pour exiger d'une classe, qu'elle possède des capacités,
pouvant être transversales à de nombreuses classes différentes. La notion de capacité
se retrouve dans le fait qu'une classe peut implémenter plusieurs interfaces.
- on préferera une classe abstraite pour modéliser le dénominateur commun à plusieurs classes
de même nature. Une classe ne peut hériter que d'une seule autre classe.
Exemple d'application
On veut coder des algorithmes opérant sur des graphes.
Mais les graphes peuvent être représentés par différentes
structures de données (matrice d'incidence, d'adjacence,
collections de noeuds et d'arêtes intereliés par des références).
On va séparer Graph (la classe offrant l'accès aux algorithmes)
et GraphStruct (l'interface implémentée par les différentes
structures de données de graphe); une instance de Graph
manipulera une instance de GraphStruct.
On a déjà implémenté un calcul du nombre de composantes connexes
comme preuve de concept dans Graph.java.
Ex.4. Interface (5 min)
- Créez une classe appelée Node. Il est raisonnable de choisir qu'un
noeud possède au moins un numéro qui l'identifie dans le graphe.
- Créez une interface appelée GraphStruct possédant deux méthodes:
- Node[] getNodes() (renvoie l'ensemble des noeuds),
- Node[] getNeighbors(Node aNode) (renvoie les voisins d'un noeud donné).
- Compilez les classes Graph, GraphStruct, Node pour s'assurer
que les noms concordent.
Ex.5. Structure de données (15 min)
- Ecrivez une classe appelée GraphStructByAdjMat, qui étend
SquareMatrix et qui
implémente l'interface GraphStruct.
Ex.6. Test (5 min)
- Ecrivez du code client pour tester le calcul du nombre de composantes connexes:
- sur trois noeuds isolés (3 composantes)
- sur trois noeuds dont deux sont reliés par une arête (2 composantes),
- etc.
Ce qu'il faut retenir
- Une interface définit ce que sait faire les classes qui l'implémentent. Une classe
peut implémenter plusieurs interfaces.
- Une classe mère définit un dénominateur commun qu'enrichissent ses classes filles.
Une classe ne peut dériver que d'une seule classe. Quand la classe mère est abstraite,
elle n'est pas instanciable.
- Dans les deux cas, il y a polymorphisme: les objets de même type sont interchangeables.
- Pour écrire du code générique et réutilisable, mieux vaut programmer pour une interface,
plutôt que pour des objets particuliers.
Héritage de classe ?
Finalement, pour savoir si l'héritage entre deux classes est approprié,
l'important est de se demander si on veut exploiter
- la propriété d'extension de code.
- la propriété de polymorphisme,
Si on veut exploiter ces deux propriétés en même temps,
l'héritage convient (mais c'est plutôt rare).
Dans le cas 1., une relation de composition pourrait être préférée.
Dans le cas 2., mieux vaut considérer l'utilisation d'une classe abstraite ou d'une interface.