Suivez-nous sur X

|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Programmation objet, plus loin avec les objets
(Article écrit par Onlytoine et extrait de GuruMed.net - janvier 2003)
|
|
1. Introduction
Dans l'article précédent,
nous avons vu ce qu'était un objet en C++ et comment l'utiliser. Nous irons plus loin avec cet article en
comprenant les possibilités que la programmation objet du C++ nous permet. Attention : dans les exemples,
certaines libertés ont été prises, ils ne contiennent peut-être pas tous les tests nécessaires à un code "propre"
mais nous avons privilégié la clarté dans un but éducatif.
2. Après le Rectangle, l'Ellipse !
Après avoir conçu et développé la classe Rectangle en C++, vous trouvez ça génial et vous avez envie
de faire de même avec une Ellipse ! (si vous avez effectivement pensé ça, la visite chez un psy approche).
En partant sur le même modèle que le Rectangle, vous seriez tentés d'écrire rapidement la classe Ellipse ainsi :
#include <math.h>
#define PI 3.14159265
class Ellipse {
// *** Attributes ***
private:
int x, y;
int width, height;
// *** Constructor/Destructor ***
public:
Ellipse(int x=0, int y=0, int w=100, int h=100) {
this->setX(x);
this->setY(y);
this->setWidth(w);
this->setHeight(h);
};
~Ellipse() {
// Rien à faire
};
// *** Getters ***
public:
int getX() { return this->x; };
int getY() { return this->y; };
int getWidth() { return this->width; };
int getHeight() { return this->height; };
// *** Setters ***
public:
void setX(int x) { this->x = x; };
void setY(int y) { this->y = y; };
void setWidth(int w) { this->width = w; };
void setHeight(int h) { this->height = h; };
// *** Business' Methods ***
public:
int getPerimeter() {
return (int)(2*PI*(sqrt(0.5*(this->height^2 + this->width^2))));
};
int getArea() {
return (int)((PI/4) * this->width * this->height);
};
};
|
Et c'est à ce moment-là, après avoir regardé le code que l'on vient d'écrire (ou copier/coller ou modifier)
qu'on se dit que ça ressemble beaucoup à la classe Rectangle du précédent article ! En effet, les deux classes ont un point
commun : ce sont des formes géométriques qui ont donc des propriétés communes comme une aire et un périmètre.
La programmation objet du C++ arrive alors à la rescousse afin d'écrire ces classes plus intelligemment
en permettant :
- De ne pas dupliquer du code identique dans Rectangle et Ellipse.
- D'exprimer le fait que ces classes font parties de la même famille.
3. Rectangle et Ellipse sont des Shapes
Et oui, le point commun à Rectangle et Ellipse est que ce sont des formes géométriques. On peut
exprimer cette base commune grâce à l'héritage. L'idée est de définir la base de toutes formes géométriques
dans une classe nommée Shape ("Forme" en français) et ainsi faire -dériver- Rectangle et Ellipse
de cette classe. La dérivation permet à une classe, dite fille, de bénéficier directement des données
et des méthodes de la classe dite mère. Plutôt qu'un long discours, voilà à quoi ressemblerait notre classe Shape :
class Shape {
// *** Attributes ***
protected:
int x, y;
int width, height;
// *** Constructor/Destructor ***
public:
Shape(int x=0, int y=0, int w=100, int h=100) {
this->setX(x);
this->setY(y);
this->setWidth(w);
this->setHeight(h);
};
~Shape() {
// Rien à faire
};
// *** Getters ***
public:
int getX() { return this->x; };
int getY() { return this->y; };
int getWidth() { return this->width; };
int getHeight() { return this->height; };
// *** Setters ***
public:
void setX(int x) { this->x = x; };
void setY(int y) { this->y = y; };
void setWidth(int w) { this->width = w; };
void setHeight(int h) { this->height = h; };
// *** Business' Methods ***
public:
virtual int getPerimeter() = 0;
virtual int getArea() = 0;
};
|
Alors, voyons les nouveautés. Vous remarquerez premièrement que le mot-clé "private" a été remplacé
par le mot-clé "protected". Pourquoi ? Simplement parce que "protected" permet d'accéder aux données/méthodes
dans les classes dérivées ; "private" rend les données/méthodes visibles uniquement dans la classe où elles
sont définies.
Deuxièmement, "virtual" fait son apparition devant les méthodes de calcul. La méthode est alors dite "virtuelle",
c'est-à-dire qu'elle sera exécutée selon la vraie classe de l'objet qui l'appelle. La terminaison "= 0"
en fait une méthode virtuelle "pure" et indique que la méthode est "abstraite", qu'elle ne dispose pas de code
associé. Ainsi, la classe "Shape" ne peut pas être instanciée : new Shape() n'a pas de sens. Elle va servir
de base commune à d'autres classes.
4. Refonte sur Rectangle et Ellipse
Maintenant que nous avons réussi à détacher la notion de forme géométrique avec Shape, nous allons
réimplémenter Rectangle et Ellipse en conséquence grâce au mécanisme d'héritage.
#include "Shape.h"
class Rectangle : Shape {
// *** Constructor/Destructor ***
public:
Rectangle(int x, int y, int w, int h) : Shape(x, y, w, h) {};
~Rectangle() {};
// *** Business' Methods ***
public:
int getPerimeter() {
return this->width*2 + this->height*2;
};
int getArea() {
return this->width * this->height;
};
};
class Ellipse : Shape {
// *** Constructor/Destructor ***
public:
Ellipse(int x, int y, int w, int h) : Shape(x, y, w, h) {};
~Ellipse() {};
// *** Business' Methods ***
public:
int getPerimeter() {
return (int)(2*PI*(sqrt(0.5*(this->height^2 + this->width^2))));
};
int getArea() {
return (int)((PI/4) * this->width * this->height);
};
};
|
L'héritage se fait simplement en mentionnant, derrière le nom de la classe, le nom des classes dont
elles dérivent (le C++ permet l'héritage multiple, contrairement à Java par exemple). L'ensemble des
attributs et des méthodes "public" ou "protected" est alors disponible dans la classe fille (celle qui
hérite). Les classes filles de Shape, Rectangle et Ellipse, peuvent également surcharger certaines
méthodes afin de modifier leur comportement... Nous verrons ça plus tard. :o)
Le gain est tout de suite visible : toute nouvelle forme géométrique peut se baser sur Shape et ajouter
ses données et ses méthodes propres. Exemple : Ellipse pourrait bénéficier en plus de "float getExcentricity()".
De plus, si vous ajoutez de nouvelles méthodes à Shape qui permettent de travailler sur le centre de la
forme géométrique :
class Shape {
// ...
// *** Business' Methods ***
public:
// ...
int getCenterX() {
return this->x + (this->width/2);
}
int getCenterY() {
return this->y + (this->height/2);
}
void setCenter(int cx, int cy) {
this->x = (cx - this->width/2);
this->y = (cy - this->height/2);
}
}
|
Les nouvelles méthodes sont directement utilisables par Rectangle et Ellipse. "setCenter()" repositionne
l'origine de la forme par rapport aux coordonnées du centre demandées.
5. Programme de test
Le fait d'avoir optimisé notre développement grâce à l'héritage va nous ouvrir d'autres portes. Si vous
reprenez la fin de l'article précédent, vous pouvez jeter un oeil sur l'exemple d'utilisation. Nous avions
écrit une bête fonction "main" affichant les caractéristiques de notre Rectangle.
Dans l'exemple suivant, nous allons être plus efficaces en écrivant une fonction permettant d'afficher
les caractéristiques d'une forme géométrique (Shape) quelle qu'elle soit.
Ci-dessous, sans les virtual, printShape() ne pourrait accéder à getArea() par exemple.
void printShape(Shape* shapeToPrint) {
printf("x : %d\n", shapeToPrint->getX());
printf("y : %d\n", shapeToPrint->getY());
printf("Largeur : %d\n", shapeToPrint->getWidth());
printf("Hauteur : %d\n", shapeToPrint->getHeight());
printf("Périmètre : %d\n", shapeToPrint->getPerimeter());
printf("Aire : %d\n", shapeToPrint->getArea());
printf("\n");
}
int main(int argc, char *argv[])
{
Rectangle* rectangle = new Rectangle(10, 15, 200, 300);
Ellipse* ellipse = new Ellipse(10, 15, 200, 300);
printShape(rectangle);
printShape(ellipse);
delete(rectangle);
delete(ellipse);
}
|
Comme vous l'avez vu, la fonction "printShape(Shape*)" s'attend à recevoir un objet de type Shape en paramètre.
Dans notre fonction "main", nous instancions un Rectangle et une Ellipse puis nous appelons la fonction
"printShape(Shape*)" afin d'obtenir les caractéristiques de nos deux formes. Ceci est possible car Rectangle et
Ellipse sont des Shapes. La fonction "printShape(Shape*)" se voit attribuer le nom barbare de fonction polymorphique
car elle peut recevoir des paramètres de types différents (Rectangle, Ellipse, tout ce qui dérivera de Shape).
6. Conclusion et entraînement
Voilà donc quelques autres aspects de la programmation objet en C++. L'héritage est très pratique et simple
à mettre en oeuvre. Le "polymorphisme" ou la possibilité de voir différemment un objet est une fonctionnalité
qui permet :
- D'écrire des fonctions applicables à une vaste famille d'objets.
- De pouvoir surcharger (redéfinir) des méthodes lorsque l'on dérive d'une classe.
Un petit exercice pour terminer, très simple :
- Ajouter une méthode virtuelle à la classe Shape qui renverrait le nom de la forme géométrique.
- Définir cette méthode dans Rectangle et Ellipse.
- Modifier la fonction "printShape(Shape*)" afin qu'elle affiche le nom de la forme géométrique.
|