L'HERITAGE ET LE POLYMORPHISME Après l'encapsulation, l'héritage est le deuxième aspect fondamental de la programmation objet. C'est de l'association de ces deux aspects que la programmation objet tire sa véritable force, à savoir permettre une réelle réutilisabilité du code. Grâce à l'héritage, on peut créer une nouvelle classe à partir d'une classe existante et ainsi en récupérer les attributs et les méthodes, sans avoir à la réécrire complètement. L'héritage Jusqu'à présent, nous avons construit des classes de toutes pièces, en partant quasiment de rien, si ce n'est des types de base du langage. Or, il est possible de définir une classe sur la base d'une autre classe grâce au mécanisme de l'héritage. La classe de base, appelée classe mère ou super-classe, transfert toutes ses caractéristiques (attributs et méthodes) à la nouvelle classe, appelée classe fille ou sous-classe. On dit que la classe fille hérite de la classe mère, ou encore que la classe fille dérive de la classe mère. La classe fille - peut utiliser tous les attributs et les méthodes de sa classe mère - et possède en plus des attributs et méthodes propres. L'héritage est une relation du type "est une sorte de" ou même "est un" Un objet de la classe fille est un objet de la classe mère avec des caractéristiques supplémentaires. A chaque fois qu'on créé un objet dérivé, on crée aussi un objet de base. L'héritage est un mécanisme permettant de réutiliser une classe existante. Il permet de transférer les caractéristiques d'une classe mère à une classe fille. Deux raisons peuvent nous amener à constituer des classes sur la base d'autres classes. ce sont: - la spécialisation - et la généralisation Exemple 1 fondé sur la spécialisation Reprenons le thème des comptes bancaires. Supposons que la banque propose maintenant des comptes d'épargne en plus des comptes traditionnels. Un compte d'épargne est une sorte de compte qui possède en plus un certain taux d'intérêt. Pour créer une classe pour les comptes d'épargne, nous allons réutiliser la classe compte en utilisant l'héritage. La classe compteepargne possédera automatiquement toutes les caractéristiques de la classe compte plus celles que nous allons lui ajouter. Pour définir une classe qui dérive d'une autre, on ne réécrit pas tous les attributs et les méthodes qui existent déjà dans la classe mère, ce qu'on appelle les membres dérivés. On ne déclare que les membres supplémentaires, propres à la sous-classe. classe compteepargne hérite de compte attribut privé taux_intérêt : réel méthodes publiques Fonction gettaux ( ): réel Procédure settaux (nvtaux: réel) finclasse
Les attributs et les méthodes dérivées de la classe compte (nom, numéro, solde, getnom(), ) n'apparaissent pas à la déclaration de la sous-classe mais ils existent. Les membres dérivés sont implicites. Représentation en UML numero nom solde compte ( ) créditer( ) débiter( ) getsolde ( ) compte relation est une sorte de hérite de dérive de classe de base classe mère super-classe sous-classe classe fille classe dérivée compteepargne taux_intérêt gettaux ( ) settaux ( ) attribut supplémentaire méthodes supplémentaires Pour trouver la classe compteepargne, nous avons utilisé la démarche de spécialisation: à partir d'une classe de base, nous avons trouvé une classe plus "spécialisée" que nous avons fait dériver de cette classe de base. Exemple 2 fondé sur la généralisation Considérons qu'après une première analyse, nous ayons défini les deux classes suivantes: BouteilleDeLait Contenance DateMiseEnBout DatePéremption EstVide ( ) EstPérimée ( ) BouteilleDeVin Contenance DateMiseEnBout Couleur EstVide ( )
Nous constatons que BouteilleDeLait et BouteilleDeVin ont des points communs. Nous pouvons "factoriser" ces caractéristiques communes en créant une super-classe commune. Cela évite de définir plusieurs fois ces caractéristiques. Bouteille Contenance DateMiseEnBout EstVide ( ) BouteilleDeLait DatePéremption BouteilleDeVin Couleur EstPérimée ( ) Déclaration des classes en algorithmique Classe Bouteille Contenance : réel DateMiseEnBout: entier méthode publique Fonction EstVide( ): booléen Classe BouteilleDeLait hérite de Bouteille attribut privé DatePéremption : Date Méthode publique Fonction EstPérimée( ): booléen Classe BouteilleDeVin hérite de Bouteille Couleur : chaîne Dans le programme principal, les attributs et les méthodes dérivées (de la classe mère) sont accessibles normalement, tout comme s'ils avaient été déclarés explicitement dans la sousclasse. Remarque: en algorithmique, on fera l'hypothèse que les membres de la classe mère sont toujours accessibles à ses classes filles, même si ces attributs sont privés (comme en Pascal). Mais en C++ et en Java, ce n'est pas le cas: les membres privés de la classe mère sont inaccessibles aux classes filles. Pour éviter cela, il faut déclarer les membres protégés et non privés.
La redéfinition des méthodes et le polymorphisme Certaines méthodes d une super classe peuvent être redéfinies dans les sous classes issues de celle-ci. La sous-classe aura tout simplement une méthode qui porte le même nom et la même signature qu'une méthode de sa classe mère. Ces méthodes sont dites polymorphes ou encore virtuelles. Pour signifier qu'une méthode est redéfinie, on la fait précéder d'une étoile (notation du Pascal. En C++, une telle méthode est préfixée par virtual). Exemple: Soit une classe personne et une classe employée qui hérite de personne. Classe Personne Nom :chaîne Prénom : chaîne Méthodes publiques Constructeur Personne(leNom: chaîne, leprenom: chaîne) *Procédure Affiche( ) Classe Employé Hérite de Personne Employeur : chaîne Méthodes publiques Constructeur Employé(leNom: chaîne, leprenom: chaîne, l_employeur: chaîne) *Procédure Affiche( ) Utiliser dans une classe fille, une méthode de la classe parent Dans la classe fille, il est évidemment possible d'appeler une méthode de la classe mère (tout comme il est possible d'accéder à un attribut de la classe mère). Mais lorsque qu'une méthode est redéfinie, la méthode d'origine de la classe mère est masquée. Pourtant, il est souvent pratique d'appeler la méthode correspondante de la classe mère. Il existe donc une façon de faire appel tout de même à la méthode de la classe mère: il suffit de faire précéder l'appel de cette méthode du mot clé super. Exemple suite: *Procédure Personne : : Affiche( ) Afficher "Je suis ", Prénom, Nom FinProcédure *Procédure Employé ::Affiche( ) super.afficher( ) Afficher "et je travaille chez ", Employeur FinProc Utiliser un constructeur de la classe mère dans un constructeur de la classe fille. Les constructeurs d'une classe fille peuvent utiliser les constructeurs de la classe mère. On fait appel à un constructeur de la classe mère dans un constructeur de la classe fille grâce au mot
clé super suivi directement des paramètres à passer au constructeur de la classe mère (pas besoin de mettre le nom de la classe mère après super). Exemple suite: Constructeur Personne(leNom: chaîne, leprenom: chaîne) Nom lenom Prenom leprenom FinConstr Constructeur Employé(leNom: chaîne, leprenom: chaîne, l_employeur: chaîne) super(lenom, leprenom) Employeur l_employeur FinConstr Attention à ne pas confondre surcharge et polymorphisme! Dans le mécanisme de surcharge, c'est la signature qui différencie l'appel de deux méthodes de même nom. Dans le mécanisme de polymorphisme, c'est le type de l'objet auquel est appliqué la méthode qui entraîne l'appel de la bonne variante de la méthode. Exemple Complet Les classes abstraites Soit le graphe d'héritage suivant: FIGURE abscisse ordonnée couleur déplacer( ) getcouleur( ) setcouleur( ) aire( ) affiche( ) RECTANGLE longueur largeur getlongueur( ) getlargeur( ) ChangerDim() rayon CERCLE getrayon( ) setrayon( )
Comment définir ces classes? (Dans un premier temps, nous ferons abstraction des constructeurs) Pour les sous-classes, aucuns problèmes. Etudions la définition de la classe figure. Classe Figure ABSTRAITE abscisse : entier ordonnée : entier couleur : chaîne méthodes publiques Procédure déplacer(varabs:entier, varord:entier ) Fonction getcouleur( ) Procédure setcouleur( ) Fonction ABSTRAITE aire( ): réel Procédure ABSTRAITE affiche( ) En revanche, l'implémentation des méthodes aire et affiche n'est pas possible dans la classe figure car leur diffère selon que la figure est un Rectangle ou un carré. En fait, la classe Figure n'est pas instanciable : on ne peut pas directement créer d'objet de classe Figure: les seuls objets de type figure sont objets dérivés de Figure: soit Rectangle, soit Cercle. Il faut donc redéfinir ces méthodes pour les sous-classes Cercle et Rectangle Fonction Rectangle :: aire( ) : réel retourne longueur * largeur FinFonction Fonction Cercle :: aire( ) : réel retourne 2*3.14*rayon FinFonction Procédure Rectangle :: affiche( ) Afficher "longueur", longueur, "largeur", largeur, "aire", aire( ), FinProc Procédure Cercle :: affiche( ) L'implémentation des méthodes déplacer et getcouleur non plus. Mais comment impl