Fabio [email protected]
Programmation Orientée Objet en C++Programmation Orientée Objet en C++
12ème Partie: Polymorphisme12ème Partie: Polymorphisme
© 1997-2003 Fabio HERNANDEZ401POO en C++: Polymorphisme
Vue d'EnsembleVue d'Ensemble
Notions de base Types, variables, opérateursContrôle d'exécutionFonctionsMémoire dynamiqueQualité du logicielEvolution du modèle objet Objets et classesFonctions membresClasses génériquesHéritagePolymorphismeHéritage multipleEntrée/sortie
© 1997-2003 Fabio HERNANDEZ402POO en C++: Polymorphisme
Table des MatièresTable des Matières
MotivationAffectation polymorpheStructures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ403POO en C++: Polymorphisme
MotivationMotivation
Le mécanisme d'héritage permet aux sous-classes d'utiliser l'implémentation des méthodes de la classe de baseNous allons étudier un mécanisme étroitement lié à l'héritage appelé polymorphismePolymorphisme signifie la possibilité d ’un objet de prendre plusieurs formesDans 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érentesDans le cas de C++, cette "entité" ne peut être qu'un pointeur ou une référence
© 1997-2003 Fabio HERNANDEZ404POO en C++: Polymorphisme
Motivation (suite)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
© 1997-2003 Fabio HERNANDEZ405POO en C++: Polymorphisme
Contrôle d'AvancementContrôle d'Avancement
MotivationAffectation polymorphe Structures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ406POO en C++: Polymorphisme
Affectation PolymorpheAffectation Polymorphe
Figure
OpenFigure ClosedFigure
Polygon Ellipse
CircleTriangle Rectangle
Square
Segment Polyline
...
© 1997-2003 Fabio HERNANDEZ407POO en C++: Polymorphisme
Affectation Polymorphe (suite)Affectation Polymorphe (suite)
Si nous déclarons les objetsPolygon aPolygon;
Triangle aTriangle;
Square aSquare;
Nous pouvons déclarer le pointeurPolygon* polygonPtr;
Et les affectations suivantes sont validespolygonPtr = &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 dePolygon
© 1997-2003 Fabio HERNANDEZ408POO en C++: Polymorphisme
Affectation Polymorphe (suite)Affectation Polymorphe (suite)
Notez qu'il n'y a aucune transformation des objets aTriangleet aSquare
une fois crée un objet ne change pas son typeLes pointeurs et références peuvent être "attachés" à des objets de types différents descendants d'un ancêtre communPour le passage de paramètres nous pouvons utiliser le même principe
soit la fonctionvoid inspect(const Polygon& aPolygon)
{
// Do something with the parameter object
}
© 1997-2003 Fabio HERNANDEZ409POO en C++: Polymorphisme
Affectation Polymorphe (suite)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
© 1997-2003 Fabio HERNANDEZ410POO en C++: Polymorphisme
Contrôle d'AvancementContrôle d'Avancement
MotivationAffectation polymorphe Structures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ411POO en C++: Polymorphisme
Structures de Données PolymorphesStructures de Données Polymorphes
Soit le tableauPolygon* polygonArray[4];
polygonArray[0] = new Rectangle;
polygonArray[1] = new Square;
polygonArray[2] = new Triangle;
polygonArray[3] = new Polygon;
Nous pouvons le visualiser comme
01
2
3polygonArray
© 1997-2003 Fabio HERNANDEZ412POO en C++: Polymorphisme
Structures de Données Polymorphes (suite)Structures de Données Polymorphes (suite)
Une structure polymorphe est une structure qui contient des objets de types différents descendants d'une classe communeTous 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 contenusSupposons par exemple que nous voulons calculer la somme des périmètres des polygones contenus dans notre tableauUne 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
© 1997-2003 Fabio HERNANDEZ413POO en C++: Polymorphisme
Structures de Données Polymorphes (suite)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é parpolygonArray[pos] pour obtenir son périmètre?Regardons la définition de la classe Polygon
© 1997-2003 Fabio HERNANDEZ414POO en C++: Polymorphisme
ClasseClasse PolygonPolygon
#include "Point.h"
#include "List.h"
class Polygon {
public:
// Constructors/Destructor
Polygon();
~Polygon();
// Modifiersvoid translate(float horizontal, float vertical);void rotate(float angle);...
© 1997-2003 Fabio HERNANDEZ415POO en C++: Polymorphisme
ClasseClasse PolygonPolygon (suite)(suite)
// Selectorsfloat perimeter() const;float area() const;...
private:
// Data members
List<Point> vertexList_;
};
© 1997-2003 Fabio HERNANDEZ416POO en C++: Polymorphisme
ClasseClasse PolygonPolygon (suite)(suite)
L'implémentation de la fonction membre Polygon::perimeterpourrait ê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);
}
© 1997-2003 Fabio HERNANDEZ417POO en C++: Polymorphisme
Classe Classe RectangleRectangle
#include "Point.h"
#include "Polygon.h"
class Rectangle: public Polygon {
public:
// Constructors/Destructor
Rectangle(const Point& origin,
float side1,
float side2);
~Rectangle();
// Modifiers...
side1
side2
origin
© 1997-2003 Fabio HERNANDEZ418POO en C++: Polymorphisme
Classe Classe RectangleRectangle (suite)(suite)
// Selectorsfloat perimeter() const;float area() const;float diagonal() const;...
private:
// Data members
float side1_;
float side2_;
Point origin_;
};
Fonctions et données membres spécifiques à la
classe Rectangle
© 1997-2003 Fabio HERNANDEZ419POO en C++: Polymorphisme
Classe Classe RectangleRectangle (suite)(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 dePolygon::perimeter
De façon similaire pour la méthode Rectangle::area
© 1997-2003 Fabio HERNANDEZ420POO en C++: Polymorphisme
Contrôle d'AvancementContrôle d'Avancement
MotivationAffectation polymorphe Structures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ421POO en C++: Polymorphisme
Liaison StatiqueLiaison Statique
Static BindingLa 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éthodesExemple
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
© 1997-2003 Fabio HERNANDEZ422POO en C++: Polymorphisme
Liaison Statique (suite)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()?
© 1997-2003 Fabio HERNANDEZ423POO en C++: Polymorphisme
Liaison Statique (suite)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éeDans 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()
© 1997-2003 Fabio HERNANDEZ424POO en C++: Polymorphisme
Liaison Statique (suite)Liaison Statique (suite)
De façon similaire l'instructionfloat diagonal = polyPtr->diagonal(); // ERROR
est marquée par une erreur de compilation: polyPtr est défini comme un pointeur à Polygon et la méthodePolygon::diagonal n'est pas définiePar 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
© 1997-2003 Fabio HERNANDEZ425POO en C++: Polymorphisme
Contrôle d'AvancementContrôle d'Avancement
MotivationAffectation polymorphe Structures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ426POO en C++: Polymorphisme
Liaison DynamiqueLiaison Dynamique
Dans l'exemple précédant du calcul du périmètrefloat 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 estPolygon::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 exempleif (type of polygonArray[pos] == Rectangle)
// WARNING: this is pseudo-code
pour déterminer si un objet est de type Rectangle
© 1997-2003 Fabio HERNANDEZ427POO en C++: Polymorphisme
Liaison Dynamique (suite)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();
....
}
© 1997-2003 Fabio HERNANDEZ428POO en C++: Polymorphisme
Liaison Dynamique (suite)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 êtremodifié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, ...)
© 1997-2003 Fabio HERNANDEZ429POO en C++: Polymorphisme
Liaison Dynamique (suite)Liaison Dynamique (suite)
Contrairement à d'autres langages OO, C++ utilise par défaut la liaison statiqueLe programmeur est donc responsable d'informer le compilateur que pour une ou plusieurs méthodes d'une classe il souhaite utiliser la liaison dynamiqueLe mot clé du langage pour exprimer ce concept est virtualNous devons en conséquence modifier l'interface de notre classe Polygon pour indiquer que la fonction membrePolygon::perimeter sera virtual
© 1997-2003 Fabio HERNANDEZ430POO en C++: Polymorphisme
ClasseClasse PolygonPolygon
class Polygon {public:
// Constructors/Destructor...
// Modifiers...
// Selectorsvirtual float perimeter() const;virtual float area() const;...
private:// Data members...
};
Définition des fonctions membresPerimeter et areacomme ayant liaison
dynamique
© 1997-2003 Fabio HERNANDEZ431POO en C++: Polymorphisme
ClasseClasse PolygonPolygon (suite)(suite)
Notez que l'implémentation des fonctions Polygon::perimeter et Polygon:: area reste inchangéeLes interfaces des sous-classes de Polygon (Rectangle, Triangle, ...) peuvent rester inchangées. Néanmoins, pour clarté nous allons propager la modification de l'interface dePolygon à toutes ses sous-classesRegardons le cas de la classe Rectangle
© 1997-2003 Fabio HERNANDEZ432POO en C++: Polymorphisme
Classe Classe RectangleRectangle
class Rectangle: public Polygon {public:
// Constructors/Destructor...// Modifiers...// Selectorsvirtual float perimeter() const;virtual float area() const;
virtual float diagonal() const;...
private:// Data members...
};
Propagation pour clarté de la
définition des fonctions membresPerimeter et area
comme virtual.
Définition de la méthode diagonal
comme virtual.Affecte toutes les
sous-classes de Rectangle
© 1997-2003 Fabio HERNANDEZ433POO en C++: Polymorphisme
Calcul du périmètreCalcul du périmètre
Dans l'exemple du tableau polymorphe
01
2
3
l'algorithmefloat total = 0.0;
for (int pos=0; pos < MaxPositions; pos++)
total += polygonArray[pos]->perimeter();
appellera Rectangle::perimeter(), Square::perimeter(), Triangle::perimeter() etPolygon::perimeter()
© 1997-2003 Fabio HERNANDEZ434POO en C++: Polymorphisme
Destructeur VirtuelDestructeur Virtuel
Lors de la destruction d'une structure (conteneur) polymorphe (List, Queue, Stack, Set, Bag, Tree, ...) les objets contenus seront eux aussi probablement détruitsLe 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 membresLe destructeur de la classe de base doit être défini virtual
© 1997-2003 Fabio HERNANDEZ435POO en C++: Polymorphisme
Destructeur Virtuel (suite)Destructeur Virtuel (suite)
class Polygon {
public:// Constructors/Destructor
Polygon();
virtual ~Polygon();
// Modifiers...
// Selectors...
private:// Data members...
};
Le destructeur doit être déclarévirtual.
© 1997-2003 Fabio HERNANDEZ436POO en C++: Polymorphisme
Destructeur Virtuel (suite)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
© 1997-2003 Fabio HERNANDEZ437POO en C++: Polymorphisme
Contrôle d'AvancementContrôle d'Avancement
MotivationAffectation polymorphe Structures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ438POO en C++: Polymorphisme
Méthodes AbstraitesMéthodes Abstraites
Supposons que nous voulons ajouter une méthode à la classePolygon pour déterminer si un point (x,y) se trouve à l'intérieurLe 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 souhaitableIl est néanmoins nécessaire que tous les polygones, c'est à diretoutes les sous-classes de Polygon fournissent sa propre implémentation de ce service
© 1997-2003 Fabio HERNANDEZ439POO en C++: Polymorphisme
Méthodes Abstraites (suite)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 abstraitedans la classe de baseUne méthode abstraite est une méthode pour laquelle la classe de base ne fournit pas d'implémentation
© 1997-2003 Fabio HERNANDEZ440POO en C++: Polymorphisme
Méthodes Abstraites (suite)Méthodes Abstraites (suite)
Nous pouvons définir la méthode Polygon::isInside comme abstraiteUne méthode abstraite en C++ est aussi appelée virtuelle pure
© 1997-2003 Fabio HERNANDEZ441POO en C++: Polymorphisme
Méthodes Abstraites (suite)Méthodes Abstraites (suite)
class Polygon {
public:// Constructors/Destructor...
// Modifiers...
// Selectorsvirtual bool isInside(const Point& aPoint) const = 0;...
private:// Data members...
};
Méthode Abstraite ou virtuelle pure
© 1997-2003 Fabio HERNANDEZ442POO en C++: Polymorphisme
Contrôle d'AvancementContrôle d'Avancement
MotivationAffectation polymorphe Structures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ443POO en C++: Polymorphisme
Classe AbstraiteClasse Abstraite
Une classe ayant au moins une méthode abstraite est appelée classe abstraiteIl 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
© 1997-2003 Fabio HERNANDEZ444POO en C++: Polymorphisme
Classe Abstraite (suite)Classe Abstraite (suite)
class Rectangle: public Polygon {
public:// Constructors/Destructor...
// Modifiers...
// Selectorsvirtual bool isInside(const Point& aPoint) const;...
private:// Data members...
};
© 1997-2003 Fabio HERNANDEZ445POO en C++: Polymorphisme
Classe Abstraite (suite)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;}
© 1997-2003 Fabio HERNANDEZ446POO en C++: Polymorphisme
Contrôle d'AvancementContrôle d'Avancement
MotivationAffectation polymorphe Structures de données polymorphesLiaison statiqueLiaison dynamiqueMéthodes abstraitesClasses abstraitesRésumé
© 1997-2003 Fabio HERNANDEZ447POO en C++: Polymorphisme
Résumé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érentesLa 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éthodeUne méthode abstraite ou virtuelle pure est une méthode pour laquelle la classe de base ne fournit pas d'implémentationUne classe avec une ou plusieurs méthodes abstraites est elle aussi abstraiteIl est impossible de créer une instance d'une classe abstraite
Top Related