2.5- Héritage et polymorphisme


1 Héritage

2 Fonctions virtuelles

3 Héritage multiple

4 Type de données abstraites


1 Héritage


Declaration de la dérivation La notion d'héritage est assez spontanée : certains concepts sont voisins les uns des autres et généralisables. Par exemple, des Action ou des Obligations sont des produits financiers, à ce titre, elles ont des caractéristiques communes :

Pour indiquer qu'une classe dérive d'une autre :
#include <iostream>
#include <vector>

using namespace::std;

class ActifFinancier
	{
	public : 
	ActifFinancier();
	~ActifFinancier();
	
	vector listeCours;
	double calculRentabilite(int date1,int date2);
	
	void getCours(String str);
	}

class Action : public ActifFinancier
	{
	public : 
	Action();
	~Action();
	}

class Obligation : public ActifFinancier
	{
	public : 
	Obligation(int tauxNominal, double valeurNominale,int nombreObligations, int duree);
	~Obligation();
	
	/*le taux d'interet nominal*/
	double tauxNominal;
	double valeurNominale;
	int nombreObligations;
	int duree;

	double calculInteretEcheance();
	}


Ici, un ActifFinancier a toujours une liste de cours listeCours, qu'il soit une Obligation ou une Action. Si dans la suite, si on fait une creation d'objet, on peut manipuler les cours :
Action societeGenerale;
societeGenerale.getCours("c:\\Cours\\SG.txt");

On n'instiste pas dans ce cours sur les niveaux de visibilité
Dans l'exemple, il pourrait y avoir des classes intermédaires entre Action, Obligation et ActifFinancier. On pourrait par exemple envisager que Action hérite de ActifFinancierNegotiable qui hérite de ActifFinancier, de même pour obligation. Dans ce cas, si on déclare un objet de type Action, c'est également un objet de type ActifFinancier et donc les fonctions et attributs de ActifFinancier sont utilisables de la même manière


Construction et destruction

Lors de la construction d'un objet de type Action, il y a d'abord construction d'un objet de type ActifFinancier puis construction d'un objet de type Action. En l'absence de paramètres, c'est le constructeur par défaut qui est appelé. A l'inverse, lors de la destruction d'un objet Action, c'est d'abord le desctructeur de Action qui est appelé puis celui de ActifFinancier. Il est bien sur possible de surcharger les constructeurs que ce soit une classe dérivée ou une classe qui est dérivée. Dans ce cas, on indique le constructeur de la classe mère utilisé par chacun des constructeurs des classes dérivées :

#include <iostream>
#include <vector>
#include <string>

using namespace::std;

class ActifFinancier
	{
	public : 
	ActifFinancier();
	ActifFinancier(string cheminFichier);
	~ActifFinancier();
	
	vector listeCours;
	double calculRentabilite(int date1,int date2);
	
	void getCours(string str);
	};

class Action : public ActifFinancier
	{
	public : 
	Action();
	Action(string chemin);
	~Action();
    	};

class Obligation : public ActifFinancier
	{
	public : 
	Obligation(double tauxNominal, double valeurNominale,int nombreObligations, int duree);
	Obligation(string chemin,double tauxNominal, double valeurNominale,int nombreObligations, int duree);
	~Obligation();
	
	/*le taux d'interet nominal*/
	double tauxNominal;
	double valeurNominale;
	int nombreObligations;
	int duree;

	double calculInteretEcheance();
	};

/*Les fonctions de ActifFinancier*/
ActifFinancier::ActifFinancier()
	{
	std::cout << "Le constructeur ActifFinancier " << this << "\n";
	}


ActifFinancier::ActifFinancier(string chemin)
	{
	std::cout << "Le constructeur ActifFinancier(string) " << this << "\n";
	getCours(chemin);
	}
	

void ActifFinancier::getCours(string chemin)
	{
	//on devrait definir ici une fonction de renseignement du cours depuis le chemin
	}

ActifFinancier::~ActifFinancier()
    {
    std::cout << "Destructeur de ActifFinancier " << this << "\n";
    }



/*Les fonctions de Action*/
Action::Action() : 
ActifFinancier()
	{
	std::cout << "Le constructeur Action " << this << "\n";
	}

Action::Action(string chemin) : 
ActifFinancier(chemin)
	{
	std::cout << "Le constructeur Action(string) " << this << "\n";
	}

Action::~Action()
    {
    std::cout << "Destructeur de Action " << this << "\n";
    }


/*les fonctions de Obligations*/
Obligation::Obligation(double tauxNominal, double valeurNominale,int nombreObligations, int duree) : 
ActifFinancier()
	{
	std::cout << "Le constructeur Obligation(double tauxNominal, double valeurNominale,int nombreObligations, int duree) " << this <<"\n";
	this->tauxNominal=tauxNominal;
	this->valeurNominale=valeurNominale;
	this->nombreObligations=nombreObligations;
	this->duree=duree;
	}

Obligation::Obligation(string chemin,double tauxNominal, double valeurNominale,int nombreObligations, int duree) : 
ActifFinancier(chemin)
	{
	std::cout << "Le constructeur Obligation(string chemin,double tauxNominal, double valeurNominale,int nombreObligations, int duree)" << this <<"\n";
	this->tauxNominal=tauxNominal;
	this->valeurNominale=valeurNominale;
	this->nombreObligations=nombreObligations;
	this->duree=duree;
	}
	
Obligation::~Obligation()
    {
    std::cout << "Destructeur de Obligation " << this << "\n";
    }


int main()
	{
	Action a1;
	Action a2("c:\\j2sdk\\t.txt");
	Obligation o1(0.05,100,10,5);
	Obligation o2("c:\\j2sdk\\t.txt",0.05,100,10,5);
	return 0;
	}

L'execution de ce code donne :
Le constructeur ActifFinancier 0x22ff50
Le constructeur Action 0x22ff50
Le constructeur ActifFinancier(string) 0x22ff40
Le constructeur Action(string) 0x22ff40
Le constructeur ActifFinancier 0x22fef0
Le constructeur Obligation(double tauxNominal, double valeurNominale,int nombreObligations, int duree) 0x22fef0
Le constructeur ActifFinancier(string) 0x22fec0
Le constructeur Obligation(string chemin,double tauxNominal, double valeurNominale,int nombreObligations, int duree)0x22fec0
Destructeur de Obligation 0x22fec0
Destructeur de ActifFinancier 0x22fec0
Destructeur de Obligation 0x22fef0
Destructeur de ActifFinancier 0x22fef0
Destructeur de Action 0x22ff40
Destructeur de ActifFinancier 0x22ff40
Destructeur de Action 0x22ff50
Destructeur de ActifFinancier 0x22ff50

On constate l'ordre d'exécution des contructeurs et des desctructeurs.


Substitution de fonction

Dans le cas où on déclare deux fonctionx dans la classe Action et dans la classe ActifFinancier :

class ActifFinancier
	{
	public : 
	ActifFinancier();
	ActifFinancier(string cheminFichier);
	~ActifFinancier();
	
	vector listeCours;
	double calculRentabilite(int date1,int date2);
	
	void getCours(string str);
	};

class Action : public ActifFinancier
	{
	public : 
	Action();
	Action(string chemin);
	~Action();
	void getCours(string str);
    	};
Dans ce cas, l'appel suivant :
Action a;
a.getCours("c:\\j2sd\\cours\\cours.txt");
a.ActionFinancier::getCours("c:\\j2sd\\cours\\cours.txt");

Dans ce cas, c'est la fonction définie dans la classe Action qui sera exécutée en premier. La deuxième instruction permet d'appeller la fonction définie dans ActifFinancier.
Si jamais, on crée une fonction void getCours(string str) dans Action et une fonction void getCours() dans ActifFinancier, dans ce cas :
Action a;
a.getCours("c:\\j2sd\\cours\\cours.txt");
a.getCours();
La première fonction exécutée est celle définie dans Action, la seconde est celle définie dans ActifFinancier.

2 Fonctions virtuelles

Les fonctions virtuelles permettent de manipuler des listes d'ActifFinancier en manipulant des pointeurs :

#include <iostream>
#include <vector>
#include <string>

using namespace::std;

class ActifFinancier{
public :
	double calculRentabilite(int d1,int d2);
};

class Action : public ActifFinancier{
public :
	double calculRentabilite(int d1,int d2);
};

class Obligation : public ActifFinancier{
public :
	double calculRentabilite(int d1,int d2);
};

double ActifFinancier::calculRentabilite(int d1,int d2)
	{
	std::cout << "calculRentabilie de ActifFinancier\n";
	return 0.5;
	}

double Action::calculRentabilite(int d1,int d2)
	{
	std::cout << "calculRentabilie de Action\n";
	return 1.5;
	}

double Obligation::calculRentabilite(int d1,int d2)
	{
	std::cout << "calculRentabilie de Oligation\n";
	return 2.5;
	}

int main()
	{
	ActifFinancier *af=new ActifFinancier();
	ActifFinancier *a=new Action();
	ActifFinancier *ob=new Obligation();
	af->calculRentabilite(1,2);
	a->calculRentabilite(1,2);
	ob->calculRentabilite(1,2);
	return 0;
	}



Notez d'abord qu'il est possible de manipuler des références sur des objets de type Action ou Obligation avec des pointeurs sur ActifFinancier. Ce code donne :

calculRentabilie de ActifFinancier
calculRentabilie de ActifFinancier
calculRentabilie de ActifFinancier


Les méthodes virtuelles permettent d'appeler les fonctions des classes dérivées et non pas celle de la classe mère. Ie appeler la fonction calculRentabilite de la classe Action ou Obligation lorsque le pointeur pointe sur un objet de type Action ou Obligation. Pour cela, on déclare la méthode calculRentabilite comme une méthode virtuelle, ce faisant :

#include <iostream>
#include <vector>
#include <string>

using namespace::std;

class ActifFinancier{
public :
	virtual double calculRentabilite(int d1,int d2);
};

class Action : public ActifFinancier{
public :
	double calculRentabilite(int d1,int d2);
};

class Obligation : public ActifFinancier{
public :
	double calculRentabilite(int d1,int d2);
};

double ActifFinancier::calculRentabilite(int d1,int d2)
	{
	std::cout << "calculRentabilie de ActifFinancier\n";
	return 0.5;
	}

double Action::calculRentabilite(int d1,int d2)
	{
	std::cout << "calculRentabilie de Action\n";
	return 1.5;
	}

double Obligation::calculRentabilite(int d1,int d2)
	{
	std::cout << "calculRentabilie de Obligation\n";
	return 2.5;
	}

int main()
	{
	ActifFinancier *af=new ActifFinancier();
	ActifFinancier *a=new Action();
	ActifFinancier *ob=new Obligation();
	af->calculRentabilite(1,2);
	a->calculRentabilite(1,2);
	ob->calculRentabilite(1,2);
	return 0;
	}
En ayant déclarer la méthode calculRentabilite comme virtuelle :

calculRentabilie de ActifFinancier
calculRentabilie de Action
calculRentabilie de Obligation

3 Héritage multiple

Il est possible de construire une classe héritant de plusieurs classes. Par exemple, une action est une part sociale d'entreprise. On peut donc envisager que non seulement Action hérite de ActifFinancier, mais également qu'Action hérite d'une classe PartSociale qui définit des fonctions spécifiques. Dans ce cas, la notation :

#include <string>
#include <vector>
#include <iostream>

using namespace::std;

class ActifFinancier
	{
	public : 
	vector<double>listeCours;
	double calculRentabilite(int date1,int date2);
	ActifFinancier(vector<double> listeCours);
	};

class PartSociale
	{
	public :
	int getNbPartsSociales();
	private : 
	int nbParts;
	};

class Action : public ActifFinancier, public PartSociale
	{
	public : 
	Action(vector<double> liste);
	};

ActifFinancier::ActifFinancier(vector<double> listeCours)
	{
		for(int i=0;i<listeCours.size();i++)
		{
		double y=listeCours.at(i);
		this->listeCours.push_back(y);
		}
	}

double ActifFinancier::calculRentabilite(int date1,int date2)
	{
	double x1=listeCours.at(date1);
	double x2=listeCours.at(date2);
	return (x2-x1)/x1;
	}

int PartSociale::getNbPartsSociales()
	{
	return nbParts;
	}

Action::Action(vector<double> liste) :
	ActifFinancier(liste)
	{	
	}
	
int main()
	{
	srand(time(0));
	vector<double> liste;
		for(int i=0;i<100;i++)
		liste.push_back(rand()/(double)RAND_MAX);
	Action *a=new Action(liste);
	std::cout << "Calcul rentabilite : " << a->calculRentabilite(4,6) << "\n";
	std::cout << "getNbPartsSociales : " << a->getNbPartsSociales();
	}
Notez d'abord un élément qui aurait pu être évoqué dans la partie précédente : dans le cas d'une classe Action qui dérive d'une classe ActifFinancier, le constructeur de ActifFinancier est appelé pour construire un objet de type Action. Si le constructeur sans paramètre existe pour ActifFinancier, c'est celui qui est appelé. Dans le cas où on veut appeler un autre constructeur de ActifFinancier pour construire les objets de type Action :
Action::Action(type1 para1) :
ActifFinancier(para1)
	{
	}
Dans le cas où le constructeur par défaut n'existe pas pour ActifFinancier, ce type d'appel est obligatoire.
Que se passe t'il si Action hérite de deux classes mais que certaines méthodes ou certains attributs sont communs aux deux classes desquelles on dérive ? Ie supposons par exemple que PartSociale et ActifFinancier définissent toutes les deux une fonction void f(). Dans ce cas, la simple utilisation de la fonction :
Action *a;
a->f();
Provoquera une erreur lors de la compilation. Il faudra préciser les appels :
Action *a;
a->PartSociale::f();
a->ActifFinancier::f();


Dans le cas de plusieurs constructeurs préalables à préciser :
Action::Action(type1 para1, type2 para2, type3 para3) : 
ActifFinancier(para1),
PartSociale(para2,para3)
	{
	}

4 Type de données abstraites

Dans certaines situations, une classe suppose l'existence d'un comportement, mais ce comportement ne peut être implémenté qu'au niveau des classes dérivées. Par exemple, la classe ActifFinancier appelle à la définition d'un comportement calculRentabilite(int date1,int date2), néanmoins, le calcul de la rentabilité dépend du type d'actif financier : elle ne se calcule pas de la même façon pour une obligation ou pour une action. La syntaxe est la suivante :

virtual double calculRentabilite(int date1,int date2)=0;


On va déclarer la classe ActifFinancier comme implémentant une fonction virtuelle pure : calculRentabilite alors qu'elle offre des fonctions qui ne sont pas des fonctions virtuelles pures. Les classes qui contiennent des fonctions virtuelles pures ne peuvent pas être instanciées.

Supposons que l'on gère une liste de pointeur sur ActifFinancier :

#include <iostream>
#include <vector>
#include <string>

using namespace::std;

class ActifFinancier
	{
	public : 
	vector<double> listeCours;
	virtual double calculRentabilite(int date1,int date2)=0;
	double evolutionCours(int date1,int date2);
	};

class Action : public ActifFinancier
	{
	public : 
    	double calculRentabilite(int date1,int date2);
	};

class Obligation : public ActifFinancier
	{
	public : 
	double calculRentabilite(int date1,int date2);
	};


double Action::calculRentabilite(int date1,int date2)
	{
	return 5;
	}

double Obligation::calculRentabilite(int date1,int date2)
	{
	return 3;
	}

int main()
	{
	return 0;
	}

Dans ce cas, il devient possible de gérer des collections de pointeurs sur ActifFinancier, par exemple :
vector<ActifFinancier *> actifs;
...
	for(int i=0;i<actifs.size();i++)
	{	
	ActifFinancier *aF=actifs.at(i);
	std::cout << aF->calculRentabilite(5,10) << "\n";
	}
Que se passe t'il à la compilation si on ne définit pas la fonction de calculRentabilite pour Obligation ? La compilation fonctionne tant que l'on ne cherche pas à instancier d'objet de type Obligation. Si on cherche à instancier un tel objet, cela ne fonctionne pas : une erreur de compilation se déclenche, il est impossible de créer un objet pour cette classe.