Programmation Objet en JAVA Cours 2 : Réutilisation, Héritage 1 Agrégation, composition, héritage, Hiérarchie, Constructeurs, Transtypage
Réutilisation ? Comment utiliser une classe comme matériau pour concevoir une autre classe répondant à de nouveaux besoins ? Quels sont les attentes de cette nouvelle classe ? besoin des «services» d'une classe existante (les données structurées, les méthodes, les 2). faire évoluer une classe existante (spécialiser, ajouter des fonctionnalités, ... ) Quelle relation existe entre les 2 classes ? Dans une conception objet, des relations sont définies, des règles d'association et de relation existent. Un objet fait appel un autre objet. Un objet est crée à partir d'un autre : il hérite.
La relation client/serveur - Agrégation Un objet o1 de la classe C1 utilise un objet o2 de la classe C2 via son interface (attributs, méthodes). o1 délègue une partie de son activité et de son information à o2. o2 a été construit et existe par ailleurs. Faire une référence dans C1 vers un objet de C2; Attention o2 est autonome, indépendant de o1, il peut être partagé. C'est une agrégation. Le client Le serveur public class C1 { private C2 o2; ... } public class C2 { ... }
La relation client/serveur - Agrégation Le client Le serveur public class Cercle { private Point centre; ... } public class Point { ... }
La relation client/serveur - Composition Mais si o2 évolue, est-ce grave? Comment faire pour que o1 possède son o2 à lui. La solution se trouve dans le constructeur de C1 : doter o1 d'un objet o2 qui lui appartienne. C'est une composition. Le client Le serveur public class C1 { private C2 o2; ... C1(...){ o2 = new C2(...); } public class C2 { ... }
La relation client/serveur - Composition Le client Le serveur public class Cercle { private Point centre; ... public Cercle(...){ centre = new Point(...); } public class Point { ... }
L'exemple du cercle Le cercle c1 a un centre. C'est le point p1. Utilisation d'un objet de type Point, car la classe existe déjà. Si agrégation, tout changement effectué sur p1 a des conséquences sur c1. Si on déplace p1, on déplace tous les cercle ayant comme centre p1. Composition ? Mais plusieurs cercle peuvent partager le même centre ... Impossible si composition. ça se discute ! Question : A-t-on besoin du code source de la classe Point pour coder la classe Cercle ? Besoin d’un objet existant ? « Se connecter à un serveur » Confiance en l’encapsulation Agrégation Composition et getter.
Nouveau problème Si le serveur est insuffisant, incomplet, inadapté ? un objet Point répond à un besoin «mathématique». Si on veut en faire un objet graphique, avec une couleur et qu'il soit capable de se dessiner ? Mais en gardant les propriétés et les fonctionnalités d'un Point. La solution en POO : l'héritage. Définir une nouvelle classe à partir de la définition d'une classe existante. Spécialiser, augmenter.
Héritage En java, on n'hérite que d'une seule classe. public class LaClasseFille extends LaClasseMere{...} class PointAvecUneCouleur extends Point En java, on n'hérite que d'une seule classe. Un objet de ClasseFille peut faire tout ce que sait faire un objet de ClasseMere Il le fait différemment et/ou il peut plus.
Exemple en Javadoc
Hiérarchie en Héritage L'héritage est une notion transitive Hiérarchie d'héritage : l'ensemble des classes dérivées d'un parent commun. Attention ce n'est pas bijectif : un Point3D est un Point. Le contraire est faux. Chaîne d'héritage : le chemin menant d'une classe vers ses ancêtres.
Concept d’héritage Concept fondamental des langages objet. Dériver une nouvelle classe à partir d'une classe existante en récupérant ses propriétés. Avantage Réutilisation : factorisation, enrichissement de champs et méthodes de classes existantes. Spécialisation d’une classe existante sans la modifier. Pas de limitation en profondeur. Contrainte Héritage simple : une classe ne peut hériter que d'une seule classe. Toutes les méthodes et champs ne sont plus regroupées dans le même fichier
Exemple d'héritage import Point; public class PointAvecUneCouleur extends Point { int couleur, x, y; // x et y sont hérités public PointAvecUneCouleur(int couleur) // un constructeur super(); // appel au constructeur de la classe mère setCouleur(couleur); } public PointAvecUneCouleur(int x, int y, int couleur) // un constructeur super(x,y); // appel au constructeur de la classe mère public void setCouleur(int couleur) this.couleur=couleur; // désigne l'objet encours
La classe "Object" Toutes les classes Java sont des descendants de la classe Object. Object est parent par défaut des classes qui ne précisent pas leur ancêtres Object offre quelques services génériques dont la pluspart sont à refaire, ... à redéfinir (attention au fonctionnement par défaut) : public final Class getClass(); Renvoie un objet de type Class qui permet de connaître le nom d'une classe public boolean equals(Object o); Compare les champs un par un (pas les pointeurs mémoire comme ==) protected Object clone(); Allocation de mémoire pour une nouvelle instance d'un objet et recopie de son contenu public String toString(); Renvoie une chaîne décrivant la valeur de l'objet
La classe "Class" C'est la classe qui identifie les types à l'exécution Permet la sélection des méthodes en fonction de la classe lors des appels public String getName(); Renvoie le nom d'une classe public static Class forName(String s); La fonction réciproque de getName public Object newInstance(); Crée une nouvelle instance de même type public String toString(); Renvoie une chaîne décrivant la valeur de l'objet
La méthode equals() Egalité entre objet et non entre référence Très utile pour les String If (myString == "Hello") {…} Toujours Faux !!! If (myString.equals("Hello") ) {…} OK Si les 2 objets o1 et o2 ont des références r1 et r2 comme champs, il faut que r1==r2 pour que o1.equals(o2) et non pas que r1.equals(r2) Réflexive, symétrique, transitive et consistent Pour les classes que vous créez, vous êtes responsable. Point p1 = new Point(2,1); Point p2 = new Point(2,1); if (p1==p2){...} // Ici c'est faux if (p1.equals(p2)){...}// OK si equals est bien redéfinit p1 = p2 // Co-référence if (p1==p2){...} // Vrai désormais
Les fonctions hashCode() et toString() hashCode() renvois un chiffre différent pour chaque instance différente Si o1.equals(o2) alors o1.hashCode()==o2.hashCode() Le contraire n’est pas assuré mais préférable (@mémoire) Très utile pour les HashTable. toString() conversion en String d'un objet : String s = '' mon objet visible '' + monObjet; String s = '' mon objet visible '' + monObjet.toString();
Constructeurs et héritage L'héritage permet la réutilisation de code. Le constructeur est concerné. Possibilité d'invoquer une méthode héritée : la référence super. Invocation d'un constructeur de la super-classe : super(<params du constructeur>) L'utilisation de super ressemble à celle de this. L'appel à un constructeur de la super classe doit toujours être la première instruction dans le corps du constructeur. Si ce n'est pas fait, il y un appel implicite : super(). Lors de la création d'un objet, les constructeurs sont invoqués en remontant de classe en classe la hiérarchie, jusqu'à la classe Object. Attention, l'exécution se fait alors en redescendant dans la hiérarchie. Un constructeur d'une classe est toujours appelé lorsqu'une instance de l'une de ses sous classes est créée.
Héritage et constructeurs - suite public class Object { public Object() {...} } public class Point extends Object { public Point(double i, double j) {...} } public class PointAvecUneCouleur extends Point { public PointAvecUneCouleur(int i, int j, int c){ super(i,j); couleur=c; }
Retour sur protected Dans la classe Point pour setX on a 3 possibilité : public : un objet PointAvecUneCouleur peut fixer l'abscisse, ... mais aussi tout le monde. private : plus personne ne peut, y compris un objet PointAvecUneCouleur. protected : la solution ici, les objets d'une classe héritière ont accès aux membres protected (+friendly). public void setX (double nouveauX){...} private void setX (double nouveauX){...} protected void setX (double nouveauX){...}
Transtypage et héritage : surclassement. Le transtypage consiste à convertir un objet d'une classe en objet d'une autre classe Vers une «super-class» (upcast ou surclassement), c'est toujours possible : on peut toujours transtyper vers une super-classe, plus pauvre en informations. Point3D unPoint3D = new Point3D( x, y, z ); Point unPoint = (Point) unPoint3D; // une projection sur xOy La mention du transtypage est facultative, mais recommandée pour la lisibilité. « un Point3D est un Point mais pas le contraire. » Un message envoyable à un objet, l'est aussi pour tous les objets d'une classe héritière : PointAvecUneCouleur p = new PointAvecUneCouleur(); p.translater(1,3); // Méthode définit au niveau de Point
Transtypage et héritage : surclassement. Le transtypage consiste à convertir un objet d'une classe en objet d'une autre classe Vers une «super-class» (upcast ou surclassement), c'est toujours possible : on peut toujours transtyper vers une super-classe, plus pauvre en informations. Point3D unPoint3D = new Point3D( x, y, z ); Point unPoint = (Point) unPoint3D; // une projection sur xOy La mention du transtypage est facultative, mais recommandée pour la lisibilité. Attention : unPoint3D est de type Point3D mais aussi Point. « un Point3D est un Point mais pas le contraire. » Un message envoyable à un objet, l'est aussi pour tous les objets d'une classe héritière : PointAvecUneCouleur p = new PointAvecUneCouleur(); p.translater(1,3); // Méthode définit au niveau de Point
Transtypage et héritage : sous-classement Le comportement d'un objet «hérité» est augmenté par rapport à un objet d'une classe mère. Le transtypage vers une sous-classe (downcast ou sous- classement) Impossible (sauf si un surclassement avait été fait) pour transtyper vers une sous-classe, utiliser le mot-clé réservé instanceof. unPoint = new Point3D(...); .... if( unPoint instanceof Point3D) { unPoint3D = (Point3D) unPoint; unPoint3D.projection3D(...); } La mention du transtypage est obligatoire, dans ce cas.
Un exemple : la banquise Un plateau composé de 8x8 cases Les 4 coins : terre ferme les autres cases : banquise. Lorsqu’un joueur fait fondre une case banquise, les propriétés se révèlent : certaines cases ne fondent pas et fait apparaître un renard d’autres fondent font apparaître de l’eau et un banc de poissons ou ours d’autres donnent au joueur des capacités (elles sont donc garder par le joueur) d’autres implique une action immédiate (dérive) Pour résumer : deux grande familles de Cases, action et modification du joueur. Sur une case, peuvent être posés des pions et des igloos.
banquise : questions Comment encoder la notion de plateau ? Comment encoder la notion de pion, igloo et des différentes actions possibles ? Pour les cases : Un type d’objet (sa classe) se définit par sa structure de données et son comportement. Si deux objets ont des comportements différents, ils appartiennent à deux types différents -> des classes différentes. Ce qui est générique à tous : peut contenir des pions et igloos Il y a plusieurs types de banquises, toutes ont une action lors de la fonte.
banquise : conception Toutes les banquises sont des cases. Case // Contient Pions et Igloos sous forme de vecteurs | // -> agrégation ou composition ? | |--- BanquiseFerme // la tuile change de forme, mais il ne | // se passe rien. |--- BanquiseAction // la fonte déclenche une action | // du joueur -> lui envoie un | // message spécifique |--- BanquiseCapacité // la fonte modifie le joueur // -> lui envoie un message spécifique Toutes les banquises sont des cases. Toutes les cases banquises ont une méthode action. Cette méthode est différente selon le type de la case (sa classe). Le plateau est un tableau de Cases
Référence sur une Case @0ffa88 Objet Case en mémoire Vector<Pion> pions; Igloo igloo; ... Vector<pion> getPions(); boolean addPion(); boolean addIgloo(); boolean equals(Object o); String toString(); macase Case macase = new Case ( ... );
Référence sur une BanquiseFerme @0ffa98 Objet BanquiseFerme en mémoire public BanquiseFerme extends Case Vector<Pion> pions; Igloo igloo; ... BanquiseFerme macase = new BanquiseFerme(...); Vector<pion> getPions(); boolean addPion(); boolean addIgloo(); boolean equals(Object o); String toString(); macase void action( ... );
Surclassement d’une BanquiseFerme @0ffa98 Objet BanquiseFerme en mémoire public BanquiseFerme extends Case Vector<Pion> pions; Igloo igloo; Case macase = new BanquiseFerme(null,1); Code hérité de la classe Case Vector<pion> getPions(); boolean addPion(); boolean addIgloo(); boolean equals(Object o); String toString(); macase void action( ... );
Surcharge et redéfinition (overload vs override) public class Point { ... public void maMethode(int i){...} } Surcharge Redéfinition public class Point3D { ... public void maMethode(double i){...} } public class PointCouleur { ... public void maMethode(int i){...} } Pour un objet Point3D : 2 méthodes maMethode Pour un objet PointCouleur : une seule maMethode
Redéfinition et surclassement public Case { ... public String toString(){ return « TerreFerme »; } public BanquiseFerme extends Case{ return « BanquiseFerme »; @0ffa98 Objet BanquiseFerme en mémoire Vector<Pion> pions; Igloo igloo; Code hérité de la classe Case Vector<pion> getPions(); boolean addPion(); boolean addIgloo(); macase String toString(); Case macase = new BanquiseFerme(...); System.out.println(macase); void action();
Exemple animalier manger dormir marcher courir (2 x marcher) espèce Tortue Cheval Ours courir galoper hiberner Cheval de traie Cheval de course galoper galoper
Blocage de l'héritage Mot-clé final Le mot-clé final devant une classe indique qu'elle ne peut avoir de descendance. final class Point3D { ... } Avantage Efficacité : implémentation statique possible. Sécurité : pas de redéfinition possible des méthodes pour une descendance. Inconvénients Impossibilité de sous-classer pour créer une classe de test indépendante
Conseils de conception Faire des schémas Représentant les classes et leur hierarchie Pour comprendre les interactions entre les classes (qui a besoin d’une référence sur qui) Pour savoir ou placer une fonction Pour bien penser les droits d'accès Représentant les appels de fonction Pour savoir quels objets sont traversés Pour savoir où récupérer les données à mettre en argument Attention l'héritage n'est pas une réponse à tout, la délégation est aussi utile. l'héritage est une relation is-like.