Obligement - L'Amiga au maximum

Vendredi 23 mai 2025 - 06:14  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

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


Trucs et astuces

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


Glossaire

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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


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.


[Retour en haut] / [Retour aux articles] [Article précédent]