Más contenido relacionado
La actualidad más candente (20)
Similar a Partie 12: Polymorphisme — Programmation orientée objet en C++ (12)
Partie 12: Polymorphisme — Programmation orientée objet en C++
- 2. Vue d'Ensemble
Notions de base
Types, variables, opérateurs
Contrôle d'exécution
Fonctions
Mémoire dynamique
Qualité du logiciel
Evolution du modèle objet
Objets et classes
Fonctions membres
Classes génériques
Héritage
Polymorphisme
Héritage multiple
Entrée/sortie
POO en C++: Polymorphisme 401 © 1997-2003 Fabio HERNANDEZ
- 3. Table des Matières
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 402 © 1997-2003 Fabio HERNANDEZ
- 4. Motivation
Le mécanisme d'héritage permet aux sous-classes d'utiliser
l'implémentation des méthodes de la classe de base
Nous allons étudier un mécanisme étroitement lié à l'héritage
appelé polymorphisme
Polymorphisme signifie la possibilité d ’un objet de prendre
plusieurs formes
Dans le contexte du modèle objet cela signifie qu'une "entité"
du langage peut être attachée en temps d'exécution à des
objets de classes différentes
Dans le cas de C++, cette "entité" ne peut être qu'un pointeur
ou une référence
POO en C++: Polymorphisme 403 © 1997-2003 Fabio HERNANDEZ
- 5. Motivation (suite)
Ce mécanisme permet de manipuler d'une façon uniforme un
ensemble d'objets appartenant à une même hiérarchie de
classes
POO en C++: Polymorphisme 404 © 1997-2003 Fabio HERNANDEZ
- 6. Contrôle d'Avancement
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 405 © 1997-2003 Fabio HERNANDEZ
- 7. Affectation Polymorphe
Figure
OpenFigure ClosedFigure
Segment Polyline Polygon Ellipse
Triangle Rectangle ... Circle
Square
POO en C++: Polymorphisme 406 © 1997-2003 Fabio HERNANDEZ
- 8. Affectation Polymorphe (suite)
Si nous déclarons les objets
Polygon aPolygon;
Triangle aTriangle;
Square aSquare;
Nous pouvons déclarer le pointeur
Polygon* polygonPtr;
Et les affectations suivantes sont valides
polygonPtr = &aTriangle; // aTriangle is-a Polygon
polygonPtr = &aSquare; // aSquare is-a Polygon
polygonPtr = new Rectangle; // a Rectangle is-a Polygon
Le mécanisme d'héritage nous permet de traiter une instance
de la classe Triangle ou Square comme une instance de
Polygon
POO en C++: Polymorphisme 407 © 1997-2003 Fabio HERNANDEZ
- 9. Affectation Polymorphe (suite)
Notez qu'il n'y a aucune transformation des objets aTriangle
et aSquare
une fois crée un objet ne change pas son type
Les pointeurs et références peuvent être "attachés" à des
objets de types différents descendants d'un ancêtre commun
Pour le passage de paramètres nous pouvons utiliser le même
principe
soit la fonction
void inspect(const Polygon& aPolygon)
{
// Do something with the parameter object
}
POO en C++: Polymorphisme 408 © 1997-2003 Fabio HERNANDEZ
- 10. Affectation Polymorphe (suite)
Nous pouvons appeler cette fonction avec comme argument un
objet descendant de Polygon
Square aSquare;
Triangle aTriangle;
inspect(aSquare); // OK: aSquare is a Polygon
inspect(aTriangle); // OK: aTriangle is a Polygon
Circle aCircle;
inspect(aCircle); // COMPILATION ERROR: aCircle is
// not a kind of Polygon
POO en C++: Polymorphisme 409 © 1997-2003 Fabio HERNANDEZ
- 11. Contrôle d'Avancement
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 410 © 1997-2003 Fabio HERNANDEZ
- 12. Structures de Données Polymorphes
Soit le tableau
Polygon* polygonArray[4];
polygonArray[0] = new Rectangle;
polygonArray[1] = new Square;
polygonArray[2] = new Triangle;
polygonArray[3] = new Polygon;
Nous pouvons le visualiser comme
0
1
2
3
polygonArray
POO en C++: Polymorphisme 411 © 1997-2003 Fabio HERNANDEZ
- 13. Structures de Données Polymorphes (suite)
Une structure polymorphe est une structure qui contient des
objets de types différents descendants d'une classe commune
Tous les conteneurs que nous avons étudiés peuvent être des
structures polymorphes (List, Queue, Set, Bag, Stack, ...)
L'intérêt des conteneurs polymorphes c'est qu'ils offrent la
possibilité de traiter d'une façon uniforme tous les objets
contenus
Supposons par exemple que nous voulons calculer la somme des
périmètres des polygones contenus dans notre tableau
Une façon naturelle serait de faire une boucle pour parcourir
chacune des positions du tableau en calculant le périmètre du
polygone correspondant et d'en faire l'addition
POO en C++: Polymorphisme 412 © 1997-2003 Fabio HERNANDEZ
- 14. Structures de Données Polymorphes (suite)
Calcul du périmètre (suite)
float total = 0.0;
for (int pos=0; pos < MaxPositions; pos++) {
total += Perimeter of polygonArray[pos];
}
Quelle méthode faudrait-il appeler sur l'objet pointé par
polygonArray[pos] pour obtenir son périmètre?
Regardons la définition de la classe Polygon
POO en C++: Polymorphisme 413 © 1997-2003 Fabio HERNANDEZ
- 15. Classe Polygon
#include "Point.h"
#include "List.h"
class Polygon {
public:
// Constructors/Destructor
Polygon();
~Polygon();
// Modifiers
void translate(float horizontal, float vertical);
void rotate(float angle);
...
POO en C++: Polymorphisme 414 © 1997-2003 Fabio HERNANDEZ
- 16. Classe Polygon (suite)
// Selectors
float perimeter() const;
float area() const;
...
private:
// Data members
List<Point> vertexList_;
};
POO en C++: Polymorphisme 415 © 1997-2003 Fabio HERNANDEZ
- 17. Classe Polygon (suite)
L'implémentation de la fonction membre Polygon::perimeter
pourrait être
float Polygon::perimeter() const
{
int numVertices = vertexList_.length();
float result = 0.0;
for (int i=1; i < numVertices; i++) {
const Point& previous = vertexList_.itemAt(i-1);
const Point& current = vertexList_.itemAt(i);
result += current.distanceTo(previous);
}
const Point& first = vertexList_.first();
const Point& last = vertexList_.last();
return result + first.distanceTo(last);
}
POO en C++: Polymorphisme 416 © 1997-2003 Fabio HERNANDEZ
- 18. Classe Rectangle
#include "Point.h"
#include "Polygon.h"
class Rectangle: public Polygon {
public:
side2
// Constructors/Destructor
Rectangle(const Point& origin,
float side1,
float side2); side1
~Rectangle();
origin
// Modifiers
...
POO en C++: Polymorphisme 417 © 1997-2003 Fabio HERNANDEZ
- 19. Classe Rectangle (suite)
// Selectors
float perimeter() const;
float area() const;
float diagonal() const;
...
Fonctions et
données membres
private:
spécifiques à la
// Data members classe Rectangle
float side1_;
float side2_;
Point origin_;
};
POO en C++: Polymorphisme 418 © 1997-2003 Fabio HERNANDEZ
- 20. Classe Rectangle (suite)
L'implémentation de la méthode Rectangle::perimeter est
plus simple que Polygon::perimeter
float Rectangle::perimeter() const
{
return 2*(side1_ + side2_);
}
Rectangle est donc une spécialisation de la classe Polygon et
Rectangle::perimeter est une redéfinition de
Polygon::perimeter
De façon similaire pour la méthode Rectangle::area
POO en C++: Polymorphisme 419 © 1997-2003 Fabio HERNANDEZ
- 21. Contrôle d'Avancement
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 420 © 1997-2003 Fabio HERNANDEZ
- 22. Liaison Statique
Static Binding
La liaison est le mécanisme utilisé par le compilateur pour
déterminer quelle fonction membre appeler sur un objet qui
appartient à une hiérarchie de classes lorsqu'il y a redéfinition
de méthodes
Exemple
Rectangle rect(Point(0.0, 1.0), 10.0, 15.0);
Polygon poly;
float perimeter;
perimeter = rect.perimeter();
// Rectangle::perimeter() is called
perimeter = poly.perimeter();
// Polygon::perimeter() is called
POO en C++: Polymorphisme 421 © 1997-2003 Fabio HERNANDEZ
- 23. Liaison Statique (suite)
Exemple (suite)
Polygon* polyPtr = &poly;
perimeter = polyPtr->perimeter();
// Polygon::perimeter() is called
Rectangle* rectPtr = ▭
perimeter = rectPtr->perimeter();
// Rectangle::perimeter() is called
polyPtr = ▭
perimeter = polyPtr->perimeter();
// Rectangle::perimeter() or Polygon::perimeter()?
POO en C++: Polymorphisme 422 © 1997-2003 Fabio HERNANDEZ
- 24. Liaison Statique (suite)
Exemple (suite)
La fonction membre appelée est Polygon::perimeter()
Le principe de liaison statique établit que le type de l'objet sur
lequel la méthode est appliquée détermine statiquement la
méthode appelée
Dans l'exemple précédent, le pointeur polyPtr pointe vers un
objet de la classe Polygon; en conséquence, l'instruction
polyPtr->perimeter()
se traduit par une invocation à la méthode
Polygon::perimeter()
POO en C++: Polymorphisme 423 © 1997-2003 Fabio HERNANDEZ
- 25. Liaison Statique (suite)
De façon similaire l'instruction
float diagonal = polyPtr->diagonal(); // ERROR
est marquée par une erreur de compilation: polyPtr est défini
comme un pointeur à Polygon et la méthode
Polygon::diagonal n'est pas définie
Par contre avec les instructions
Rectangle* rectPtr = ▭
float diagonal = rectPtr->diagonal(); // OK
on obtient le résultat souhaité
Ce principe de liaison (binding) est appelé statique parce que le
choix de la méthode à appeler est fait en temps de compilation
POO en C++: Polymorphisme 424 © 1997-2003 Fabio HERNANDEZ
- 26. Contrôle d'Avancement
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 425 © 1997-2003 Fabio HERNANDEZ
- 27. Liaison Dynamique
Dans l'exemple précédant du calcul du périmètre
float total = 0.0;
for (int pos=0; pos < MaxPositions; pos++)
total += polygonArray[pos]->perimeter();
la méthode qui sera appelée pour chaque objet du tableau est
Polygon::perimeter()
Supposons que nous disposons d'une méthode pour connaître en
temps d'exécution la classe d'un objet
On pourrait écrire par exemple
if (type of polygonArray[pos] == Rectangle)
// WARNING: this is pseudo-code
pour déterminer si un objet est de type Rectangle
POO en C++: Polymorphisme 426 © 1997-2003 Fabio HERNANDEZ
- 28. Liaison Dynamique (suite)
Une façon de résoudre ce problème et d'appeler la bonne
méthode serait
float total = 0.0;
for (int pos=0; pos < MaxPositions; pos++) {
// WARNING: this is pseudo-code
if (type of polygonArray[pos] == Rectangle)
total +=
((Rectangle*)polygonArray[pos])->perimeter();
else if (type of polygonArray[pos] == Triangle)
total +=
((Triangle*)polygonArray[pos])->perimeter();
....
}
POO en C++: Polymorphisme 427 © 1997-2003 Fabio HERNANDEZ
- 29. Liaison Dynamique (suite)
L'inconvénient de cette technique c'est qu'elle rend difficile
les modifications
Si une nouvelle sous-classe de Polygon est rajoutée ou une sous-classe
existante est supprimée de la hiérarchie, cette boucle doit être
modifiée
Le modèle objet propose une technique pour résoudre ce
problème appelée "Liaison Dynamique" (dynamic binding)
Par opposition au principe de liaison statique, avec la liaison
dynamique le compilateur ne peut décider en temps de
compilation quelle méthode appeler. Cette décision est prise en
temps d'exécution, par rapport à la classe de l'objet en
question (Rectangle, Triangle, Polygon, ...)
POO en C++: Polymorphisme 428 © 1997-2003 Fabio HERNANDEZ
- 30. Liaison Dynamique (suite)
Contrairement à d'autres langages OO, C++ utilise par défaut la
liaison statique
Le programmeur est donc responsable d'informer le
compilateur que pour une ou plusieurs méthodes d'une classe il
souhaite utiliser la liaison dynamique
Le mot clé du langage pour exprimer ce concept est virtual
Nous devons en conséquence modifier l'interface de notre
classe Polygon pour indiquer que la fonction membre
Polygon::perimeter sera virtual
POO en C++: Polymorphisme 429 © 1997-2003 Fabio HERNANDEZ
- 31. Classe Polygon
class Polygon {
public:
// Constructors/Destructor
...
// Modifiers
...
// Selectors Définition des
virtual float perimeter() const; fonctions membres
virtual float area() const; Perimeter et area
... comme ayant liaison
dynamique
private:
// Data members
...
};
POO en C++: Polymorphisme 430 © 1997-2003 Fabio HERNANDEZ
- 32. Classe Polygon (suite)
Notez que l'implémentation des fonctions
Polygon::perimeter et Polygon:: area reste inchangée
Les interfaces des sous-classes de Polygon (Rectangle,
Triangle, ...) peuvent rester inchangées. Néanmoins, pour
clarté nous allons propager la modification de l'interface de
Polygon à toutes ses sous-classes
Regardons le cas de la classe Rectangle
POO en C++: Polymorphisme 431 © 1997-2003 Fabio HERNANDEZ
- 33. Classe Rectangle
class Rectangle: public Polygon {
public:
Propagation pour
// Constructors/Destructor
clarté de la
... définition des
// Modifiers fonctions membres
... Perimeter et area
// Selectors comme virtual.
virtual float perimeter() const;
virtual float area() const;
Définition de la
virtual float diagonal() const;
méthode diagonal
...
comme virtual.
private: Affecte toutes les
// Data members
sous-classes de
...
Rectangle
};
POO en C++: Polymorphisme 432 © 1997-2003 Fabio HERNANDEZ
- 34. Calcul du périmètre
Dans l'exemple du tableau polymorphe
0
1
2
3
l'algorithme
float total = 0.0;
for (int pos=0; pos < MaxPositions; pos++)
total += polygonArray[pos]->perimeter();
appellera Rectangle::perimeter(),
Square::perimeter(), Triangle::perimeter() et
Polygon::perimeter()
POO en C++: Polymorphisme 433 © 1997-2003 Fabio HERNANDEZ
- 35. Destructeur Virtuel
Lors de la destruction d'une structure (conteneur) polymorphe
(List, Queue, Stack, Set, Bag, Tree, ...) les objets contenus
seront eux aussi probablement détruits
Le destructeur de chacun des objets sera utilisé
for (int pos=0; pos < MaxPositions; pos++)
delete polygonArray[pos];
Cependant, avec le destructeur nous avons le même problème
que avec la fonction membre perimeter
dans ce cas particulier, à cause de la liaison statique uniquement le
destructeur Polygon::~Polygon() sera appelé
La solution est la même que pour les autres fonctions membres
Le destructeur de la classe de base doit être défini virtual
POO en C++: Polymorphisme 434 © 1997-2003 Fabio HERNANDEZ
- 36. Destructeur Virtuel (suite)
class Polygon {
public:
// Constructors/Destructor
Polygon();
virtual ~Polygon(); Le destructeur doit
être déclaré
// Modifiers virtual.
...
// Selectors
...
private:
// Data members
...
};
POO en C++: Polymorphisme 435 © 1997-2003 Fabio HERNANDEZ
- 37. Destructeur Virtuel (suite)
De façon similaire, pour des raisons de clarté nous déclarerons
virtual le destructeur de toutes les sous-classes de Polygon
D'une façon générale, on doit déclarer virtual le destructeur
de toute classe contenant au moins une fonction membre
virtuelle
POO en C++: Polymorphisme 436 © 1997-2003 Fabio HERNANDEZ
- 38. Contrôle d'Avancement
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 437 © 1997-2003 Fabio HERNANDEZ
- 39. Méthodes Abstraites
Supposons que nous voulons ajouter une méthode à la classe
Polygon pour déterminer si un point (x,y) se trouve à
l'intérieur
Le prototype de cette méthode pourrait être
bool Polygon::isInside(const Point& aPoint) const
Un tel service est facilement implémenté pour certaines sous-
classes de Polygon (Triangle, Rectangle, Square)
L'implémentation est plus difficile pour un polygone générique
une implémentation par défaut n'est pas souhaitable
Il est néanmoins nécessaire que tous les polygones, c'est à dire
toutes les sous-classes de Polygon fournissent sa propre
implémentation de ce service
POO en C++: Polymorphisme 438 © 1997-2003 Fabio HERNANDEZ
- 40. Méthodes Abstraites (suite)
Une façon de faire c'est d'implémenter le service dans la
classe de base avec une implémentation par défaut "vide"
bool Polygon::isInside(const Point& aPoint) const
{
cerr << "You must implement this routine" << endl;
return false;
}
Une autre façon de faire c'est de forcer chaque sous-classe à
fournir ce service en déclarant la méthode comme abstraite
dans la classe de base
Une méthode abstraite est une méthode pour laquelle la classe
de base ne fournit pas d'implémentation
POO en C++: Polymorphisme 439 © 1997-2003 Fabio HERNANDEZ
- 41. Méthodes Abstraites (suite)
Nous pouvons définir la méthode Polygon::isInside comme
abstraite
Une méthode abstraite en C++ est aussi appelée virtuelle pure
POO en C++: Polymorphisme 440 © 1997-2003 Fabio HERNANDEZ
- 42. Méthodes Abstraites (suite)
class Polygon {
public:
// Constructors/Destructor Méthode Abstraite
...
ou virtuelle pure
// Modifiers
...
// Selectors
virtual bool isInside(const Point& aPoint) const = 0;
...
private:
// Data members
...
};
POO en C++: Polymorphisme 441 © 1997-2003 Fabio HERNANDEZ
- 43. Contrôle d'Avancement
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 442 © 1997-2003 Fabio HERNANDEZ
- 44. Classe Abstraite
Une classe ayant au moins une méthode abstraite est appelée
classe abstraite
Il est impossible de créer une instance d'une classe abstraite
Cette vérification est effectuée par le compilateur
Polygon aPolygon;
// ERROR: Polygon has at least one pure virtual
// member fonction
Les sous-classes (instanciables) de Polygon doivent fournir une
implémentation de cette méthode
POO en C++: Polymorphisme 443 © 1997-2003 Fabio HERNANDEZ
- 45. Classe Abstraite (suite)
class Rectangle: public Polygon {
public:
// Constructors/Destructor
...
// Modifiers
...
// Selectors
virtual bool isInside(const Point& aPoint) const;
...
private:
// Data members
...
};
POO en C++: Polymorphisme 444 © 1997-2003 Fabio HERNANDEZ
- 46. Classe Abstraite (suite)
bool Rectangle::isInside(const Point& aPoint) const
{
if ((origin_.getX() <= aPoint. getX()) &&
(aPoint. getX() <= (origin_. getX() + side1_)) &&
(origin_.getY() <= aPoint. getY()) &&
(aPoint. getY() <= (origin_. getY() + side2_)))
return true;
return false;
}
POO en C++: Polymorphisme 445 © 1997-2003 Fabio HERNANDEZ
- 47. Contrôle d'Avancement
Motivation
Affectation polymorphe
Structures de données polymorphes
Liaison statique
Liaison dynamique
Méthodes abstraites
Classes abstraites
Résumé
POO en C++: Polymorphisme 446 © 1997-2003 Fabio HERNANDEZ
- 48. Résumé
Le polymorphisme permet à une référence ou à un pointeur
d'être associé en temps d'exécution à des instances de classes
différentes
La liaison dynamique est le mécanisme qui permet de déterminer
en temps d'exécution l'utilisation de la redéfinition correcte
d'une méthode
Une méthode abstraite ou virtuelle pure est une méthode pour
laquelle la classe de base ne fournit pas d'implémentation
Une classe avec une ou plusieurs méthodes abstraites est elle
aussi abstraite
Il est impossible de créer une instance d'une classe abstraite
POO en C++: Polymorphisme 447 © 1997-2003 Fabio HERNANDEZ