partie 11: héritage — programmation orientée objet en c++
DESCRIPTION
Support material for a continued education course "Introduction to object oriented programming in C++". In French.TRANSCRIPT
Fabio [email protected]
Programmation Orientée Objet en C++Programmation Orientée Objet en C++
11ème Partie: Héritage11ème Partie: Héritage
© 1997-2003 Fabio HERNANDEZ345POO en C++: Héritage
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 HERNANDEZ346POO en C++: Héritage
Table des MatièresTable des Matières
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ347POO en C++: Héritage
MotivationMotivation
Assez souvent, les nouveaux logiciels sont basés sur des développements précédentsPlusieurs approches
imitationraffinementcombinaison
Le modèle objet tient compte de ce fait et offre des mécanismes pour apporter une solution à ce problèmeLes techniques étudiées jusqu'à présent ne sont pas suffisantesLes classes constituent une bonne technique de décomposition en modules
© 1997-2003 Fabio HERNANDEZ348POO en C++: Héritage
Motivation (suite)Motivation (suite)
Les classes possèdent plusieurs des qualités demandées aux composants réutilisables
modules cohérentsséparation entre l'interface et l'implémentationflexibilité des classes génériques
Davantage de qualités sont nécessaires pour atteindre les objectifs de réutilisation et extensibilitéNous avons besoin de mécanismes permettant d'exprimer les caractéristiques et le comportement communs existants dans des groupes de structures similaires, mais aussi de tenir compte des différences qui caractérisent les cas particuliers
© 1997-2003 Fabio HERNANDEZ349POO en C++: Héritage
Motivation (suite)Motivation (suite)
Une classe peut être une extension, une spécialisation ou une combinaison d'autres classes Le modèle objet et les langages orientés objet fournissent des mécanismes offrant des solutions à ces besoins via l'héritageentre classesL'héritage est un des composants fondamentaux de la technologie objet
© 1997-2003 Fabio HERNANDEZ350POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ351POO en C++: Héritage
ClassificationClassification
Nous voulons modéliser les comptes en banqueUn compte en banque a plusieurs caractéristiques intéressantes pour notre modèle
titulairenuméro d'identificationsoldedate de création
Quelques opérations sont souhaitables sur un compteobtenir le soldefaire un dépôtfaire un retraitfaire un virement sur un autre compte du même titulaire
© 1997-2003 Fabio HERNANDEZ352POO en C++: Héritage
Classification (suite)Classification (suite)
Nous pouvons écrire l'interface de la classe compte en banque en C++ comme
class BankAccount {
public:
// Constructors/Destructor
BankAccount(const std::string& owner);
~BankAccount();
// Modifiersbool deposit(float amount);bool withdraw(float amount);
© 1997-2003 Fabio HERNANDEZ353POO en C++: Héritage
Classification (suite)Classification (suite)
// Selectorsfloat getBalance() const;int getAccountNumber() const;const std::string& getOwner() const;const Date& getCreationDate() const;
private:
// Data members
std::string ownerName_;
float balance_;
Date creationDate_;
int accountNumber_;
};
© 1997-2003 Fabio HERNANDEZ354POO en C++: Héritage
Classification (suite)Classification (suite)
L’implémentation du constructeur et du destructeur de cette classe serait
BankAccount::BankAccount(const std::string& owner){
ownerName_ = owner;balance_ = 0.0;accountNumber_ = . . .; // Do something to assign a
// unique account number}
BankAccount::~BankAccount(){
// Nothing to do}
© 1997-2003 Fabio HERNANDEZ355POO en C++: Héritage
Classification (suite)Classification (suite)
Dans le monde réel il existe plusieurs types de comptes en banque
compte de chèquescompte d'épargneplan d'épargne logementplan de ...
Notre modèle des comptes bancaires devrait refléter cette réalitéNous aurions tendance à modéliser chacun de ces types de comptes comme une classe C++
class CheckingAccount {...};
class SavingAccount{...};
...
© 1997-2003 Fabio HERNANDEZ356POO en C++: Héritage
Classification (suite)Classification (suite)
Comment refléter le fait que tous ces types de comptes ont des caractéristiques communes
titulairesoldenuméro d'identificationdate de création...
Mais aussi des caractéristiques particulières à chaque type comme
la (im)possibilité de retraittaux d'intérêtsolde minimum...
© 1997-2003 Fabio HERNANDEZ357POO en C++: Héritage
Classification (suite)Classification (suite)
Avec les mécanismes étudiés jusqu'à présent nous avons deux possibilités:
dupliquer le code nécessaire pour modéliser ces caractéristiquescommunesfaire en sorte que la classe modélisant les caractéristiques communes soit contenue dans les autres classes
La première possibilité n'est pas envisageable parce qu'elle suppose tous les inconvénients de la duplication de codeConsidérons la deuxième: la classe SavingAccount contient un attribut de la classe BankAccount
la mission de cet attribut est d'encapsuler toute l'information relative à un compte bancaire générique (titulaire, solde, ...)
© 1997-2003 Fabio HERNANDEZ358POO en C++: Héritage
Classification (suite)Classification (suite)
class SavingAccount {
public:
// Constructors/Destructor
SavingAccount(const std::string& owner);
~SavingAccount();
// Modifiersbool deposit(float amount);bool withdraw(float amount);bool setInterestRate(float newRate);
© 1997-2003 Fabio HERNANDEZ359POO en C++: Héritage
Classification (suite)Classification (suite)
// Selectorsfloat getBalance() const;int getAccountNumber() const;const std::string& getOwner() const;const Date& getCreationDate() const;float getInterestRate() const;
private:
// Data members
BankAccount account_;
float interestRate_;
};
La classeBankAccount est
contenue dans SavingAccount
© 1997-2003 Fabio HERNANDEZ360POO en C++: Héritage
Classification (suite)Classification (suite)
L'implémentation des méthodes de la classe SavingAccountserait comme
float SavingAccount::getBalance() const{
return account_.getBalance();}
bool SavingAccount::deposit(float amount){
return account_.deposit(amount);}
Ces méthodes délèguent leur implémentation
à l'attributaccount_
© 1997-2003 Fabio HERNANDEZ361POO en C++: Héritage
Classification (suite)Classification (suite)
Implémentation de la classe SavingAccount (suite)float SavingAccount::getInterestRate() const{
return interestRate_;}
La classe CheckingAccount pourrait être implémentée d'une façon similaireL'avantage de cette approche est la réutilisation sans duplication du codeSon principal inconvénient est qu'une modification dans la classe BankAccount suppose des modifications dans toutes les classes qui utilisent ses services (SavingAccount, CheckingAccount,...)
© 1997-2003 Fabio HERNANDEZ362POO en C++: Héritage
Classification (suite)Classification (suite)
Le modèle objet fournit un mécanisme qui permet à une classe d'hériter les attributs et méthodes d'une autre classe et d'étendre ses fonctionnalités si nécessaireDans ce contexte nous pourrions définir la classe SavingAccount comme "une sorte de" BankAccountCe type de relation entre classes est connu comme une relation est-un (is-a), par opposition à une relation contient-un (has-a)Dans l'exemple précédent, la classe SavingAccount contient un BankAccount
Regardons maintenant le mécanisme fournit par C++ pour exprimer la relation est-un
© 1997-2003 Fabio HERNANDEZ363POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ364POO en C++: Héritage
SpécialisationSpécialisation
Nous allons modéliser les classes SavingAccount et CheckingAccount comme une spécialisation de la classeBankAccount
BankAccount
SavingAccount CheckingAccount
© 1997-2003 Fabio HERNANDEZ365POO en C++: Héritage
Spécialisation (suite)Spécialisation (suite)
class SavingAccount: public BankAccount {public:
// Constructors/Destructor
SavingAccount(const char* owner);~SavingAccount();
// Modifiers
bool setInterestRate(float newRate);
// Selectors
float getInterestRate() const;
private:
// Data members
float interestRate_;
};
Définition de SavingAccount
comme une spécialisation deBankAccount
© 1997-2003 Fabio HERNANDEZ366POO en C++: Héritage
Spécialisation (suite)Spécialisation (suite)
Un objet de la classe SavingAccount hérite les attributs et services de la classe BankAccount
BankAccount
ownerName_: std::stringbalance_: floatcreationDate_: DateaccountNumber_: int
deposit: boolwithdraw:boolgetBalance: floatgetAccountNumber: intgetOwner: std::stringgetCreationDate: const Date&
SavingAccount
interestRate_: float
getInterestRate: floatsetInterestRate: bool
© 1997-2003 Fabio HERNANDEZ367POO en C++: Héritage
Spécialisation (suite)Spécialisation (suite)
Un objet déclaré commeSavingAccount account;
est composé des attributs de la classe BankAccount et de la classe SavingAccount
ownerName_: std::stringbalance_: floatcreationDate_: DateaccountNumber_: int
interestRate_: float
Attributs de la partie
BankAccount
Attributs de la partie
SavingAccountaccount
© 1997-2003 Fabio HERNANDEZ368POO en C++: Héritage
Spécialisation (suite)Spécialisation (suite)
De façon similaire, un objet de la classe SavingAccount hérite les services de la classe BankAccount
Nous pouvons donc écrire// Create a SavingAccount object
SavingAccount account("Jacques Cousteau");
// Deposit an amount into this account
account.deposit(500.0);
// Print its new balance
cout << "The balance is " << account.getBalance();
// Set its interest rate
account.setInterestRate(0.03);
© 1997-2003 Fabio HERNANDEZ369POO en C++: Héritage
Spécialisation (suite)Spécialisation (suite)
BankAccount
deposit: boolwithdraw: boolgetBalance: floatgetAccountNumber: intgetOwner: std::stringgetCreationDate: const Date&
SavingAccount
setInterestRate: boolgetInterestRate: float
Classe de base ouSuperClasse
Classe dérivée ou Sous-Classe
© 1997-2003 Fabio HERNANDEZ370POO en C++: Héritage
Spécialisation (suite)Spécialisation (suite)
L'implémentation de la classe SavingAccount est composée des fonctions membres particulières à cette classe
bool SavingAccount::setInterestRate(float newRate){
interestRate_ = newRate;return true;
}
// Constructor
SavingAccount::SavingAccount(const std::string& owner)
: BankAccount(owner){
interestRate_ = 0.0;}
Initialisation de la partie
BankAccount
© 1997-2003 Fabio HERNANDEZ371POO en C++: Héritage
Spécialisation (suite)Spécialisation (suite)
La liste d'initialisation des attributs est utilisée pour passer les arguments nécessaires au constructeur de la classe de baseDans cet exemple
BankAccount n'a pas de constructeur par défautSavingAccount est une spécialisation de BankAccount
Pour créer un objet de la classe SavingAccount il est nécessaire d'initialiser sa partie BankAccount
Si la classe de base a un constructeur par défaut et la liste d'initialisation des attributs n'est pas spécifiée, c'est le constructeur par défaut qui sera utilisé pour créer la partie del'objet correspondante à la classe de base
© 1997-2003 Fabio HERNANDEZ372POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ373POO en C++: Héritage
Redéfinition des fonctions membresRedéfinition des fonctions membres
L'implémentation de BankAccount::withdraw pourrait être
bool BankAccount::withdraw(float amount)
{
if (balance_ < amount)
return false;
balance_ -= amount;
return true;
}
Celle-ci est une implémentation générique d'une opération de retrait
© 1997-2003 Fabio HERNANDEZ374POO en C++: Héritage
Redéfinition des fonctions membres (suite)Redéfinition des fonctions membres (suite)
Certains types de comptes bancaires imposent des restrictions sur
le montant du retraitle solde minimum...
C'est le cas de notre compte d'épargne: un compte de ce type exige un solde minimumLa fonction membre BankAccount::withdraw ne peut pas être utilisée telle quelle par la classe SavingAccountSavingAccount doit définir sa propre opération SavingAccount::withdraw
© 1997-2003 Fabio HERNANDEZ375POO en C++: Héritage
Redéfinition des fonctions membres (suite)Redéfinition des fonctions membres (suite)
class SavingAccount: public BankAccount {public:
// Constructors/Destructor
SavingAccount(const std::string& owner);~SavingAccount();
// Modifiers
bool setInterestRate(float newRate);
bool withdraw(float amount);
// Selectors
float getInterestRate() const;
private:
// Data members
float interestRate_;};
Redéfinition deBankAccount::withdraw
© 1997-2003 Fabio HERNANDEZ376POO en C++: Héritage
Redéfinition des fonctions membres (suite)Redéfinition des fonctions membres (suite)
bool SavingAccount::withdraw(float amount)
{
const float MinimumBalance = 500.0;
if ((getBalance() - MinimumBalance) < amount)
return false;
return BankAccount::withdraw(amount);
}
Appel explicite à la fonction membre withdraw de la classe de base
© 1997-2003 Fabio HERNANDEZ377POO en C++: Héritage
Redéfinition des fonctions membres (suite)Redéfinition des fonctions membres (suite)
BankAccount
deposit: boolwithdraw: boolgetBalance: floatgetAccountNumber: intgetOwner: const std::stringcreationDate: const Date&
SavingAccount
setInterestRate: boolgetInterestRate: floatwithdraw: bool
La sous-classe spécialise une des fonctions membres
de la classe de base
© 1997-2003 Fabio HERNANDEZ378POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ379POO en C++: Héritage
Conversions standardsConversions standards
Quelques conversions standards sont appliquées entre un objet d'une sous-classe et sa classe de base
valable uniquement dans le cas d'héritage avec attribut publicUn objet de la sous-classe peut être implicitement traité comme un objet de la classe de baseUn pointeur (ou une référence) à un objet de la sous-classe peut être traité comme un pointeur (ou une référence) à un objet de la classe de base
SavingAccount savingsAccount("Tantine Riche");
// the 'savingsAccount' object is a kind of BankAccount
BankAccount* bankAccountPtr = &savingsAccount;
BankAccount& bankAccountRef = &savingsAccount;
© 1997-2003 Fabio HERNANDEZ380POO en C++: Héritage
Conversions standards (suite)Conversions standards (suite)
Un objet d'une classe dérivée est composéd'une partie correspondante à la classe de based'une partie spécifique à la sous-classe
Ces conversions implicites sont acceptées parce qu’un objet d'une classe dérivée "contient" un objet de la classe de base
© 1997-2003 Fabio HERNANDEZ381POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ382POO en C++: Héritage
Contrôle d'accèsContrôle d'accès
Bien que l'attribut BankAccount::balance_ soit un des composants de la classe SavingAccount, aucune des fonctions membres de SavingAccount n'y a accès, parce qu'il a été déclaré privéSi nous ajoutons la fonction membre computeProfit à la classe SavingAccount
float SavingAccount::computeProfit() const
{
// COMPILATION ERROR: balance_ is a private
// attribute of BankAccount
return interestRate_ * balance_;
}
© 1997-2003 Fabio HERNANDEZ383POO en C++: Héritage
Contrôle d'accès (suite)Contrôle d'accès (suite)
Nous devons écrirefloat SavingAccount::computeProfit() const
{
return interestRate_ * getBalance();
}
Utilisation deBankAccount::getBalance()
© 1997-2003 Fabio HERNANDEZ384POO en C++: Héritage
Contrôle d'accès (suite)Contrôle d'accès (suite)
Il est parfois souhaitable qu'une fonction membre d'une sous-classe ait accès directement aux attributs de la classe de baseUn attribut ou méthode déclaré protected est accessible uniquement par les sous-classes
class BankAccount {public:
...
protected:
// Data membersstd::string& ownerName_;float balance_;Date creationDate_;int accountNumber_;
};
Ce mécanisme donne accès aux données
membres deBankAccount à toutes ses sous-
classes
© 1997-2003 Fabio HERNANDEZ385POO en C++: Héritage
Contrôle d'accès (suite)Contrôle d'accès (suite)
Les fonctions membres de SavingAccount ont maintenant accès aux données membres de la partie de l ’objet correspondante à la classe BankAccount
float SavingAccount::computeProfit() const
{
// OK: balance_ is a protected attribute
// of BankAccount
return interestRate_ * balance_;
}
Cependant l'accès à ces attributs reste toujours restreintBankAccount account;
account.balance_ = 200.0;
// COMPILATION ERROR: balance_ is protected
© 1997-2003 Fabio HERNANDEZ386POO en C++: Héritage
Contrôle d'accès (suite)Contrôle d'accès (suite)
Un attribut(fonction) membre déclaré(e) protected est accessible par les fonctions membres d'une classe dérivée, mais il(elle) reste privé(e) pour le reste du programme
© 1997-2003 Fabio HERNANDEZ387POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ388POO en C++: Héritage
Classe abstraiteClasse abstraite
Nous avons créé la classe BankAccount afin de "factoriser" les attributs et le comportement communs de tous les types de comptes en banque dans le but de les réutiliser dans les sous-classesEn conséquent, nous ne devrions pas avoir besoin de créer un objet de la classe BankAccount comme dans
BankAccount account("Marcel Marceau");cout << account.getBalance() << endl;
puisqu'il ne représente pas un objet du domaine du problème que nous modélisonsAutrement dit, cette classe n'a lieu d'exister qu'en tant que composante d'une classe dérivée comme SavingAccount ou CheckingAccount
© 1997-2003 Fabio HERNANDEZ389POO en C++: Héritage
Classe abstraite (suite)Classe abstraite (suite)
Nous pouvons utiliser les mécanismes de contrôle d'accès pour empêcher qu'une instance de la classe BankAccount puisse être créée
class BankAccount {
public:
// Modifiersbool deposit(float amount);bool withdraw(float amount);
// Selectorsfloat getBalance() const;int getAccountNumber() const;const std::string& getOwner() const;const Date& getCreationDate() const;
© 1997-2003 Fabio HERNANDEZ390POO en C++: Héritage
Classe abstraite (suite)Classe abstraite (suite)
protected:
// Constructors/DestructorBankAccount(const std::string& owner);
~BankAccount();
// Data members
std::string ownerName_;
float balance_;
Date creationDate_;
int accountNumber_;
};
Impossible de créer une instance de cette classe:
le constructeur et le destructeur sont
protégés
© 1997-2003 Fabio HERNANDEZ391POO en C++: Héritage
Classe abstraite (suite)Classe abstraite (suite)
Maintenant, les services de la classe BankAccount ne peuvent être utilisés que par ses sous-classes
#include "BankAccount.h"
int main() {
BankAccount account("Marcel Marceau");
// COMPILATION ERROR: the constructor
// of BankAccount is protected
return 0;
}
© 1997-2003 Fabio HERNANDEZ392POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ393POO en C++: Héritage
Redéfinition des opérateursRedéfinition des opérateurs
Une sous-classe hérite toutes les fonctions membres de sa classe de base sauf
les constructeursle destructeurles opérateurs
Les constructeurs ne sont pas hérités directement mais sont utilisés dans la liste d'initialisation des constructeurs de la sous-classeLe destructeur est utilisé automatiquement à la destruction de l'objetLes opérateurs doivent être redéfinis par la sous-classe en appelant ceux de la classe de base
© 1997-2003 Fabio HERNANDEZ394POO en C++: Héritage
Redéfinition des opérateurs (suite)Redéfinition des opérateurs (suite)
class Base {
public:
...
bool operator==(const Base& aBase) const;
Base& operator=(const Base& aBase);
...
private:
int data_;
};
© 1997-2003 Fabio HERNANDEZ395POO en C++: Héritage
Redéfinition des opérateurs (suite)Redéfinition des opérateurs (suite)
bool Base::operator==(const Base& aBase) const
{return (data_ == aBase.data_) ? true : false;
}
Base& Base::operator=(const Base& aBase)
{
if (this == &aBase)
return *this;
data_ = aBase.data_;
return *this;
}
© 1997-2003 Fabio HERNANDEZ396POO en C++: Héritage
Redéfinition des opérateurs (suite)Redéfinition des opérateurs (suite)
class Derived: public Base {
public:
...
bool operator==(const Derived& aDerived) const;
Derived& operator=(const Derived& aDerived);
...
private:
float ratio_;
};
© 1997-2003 Fabio HERNANDEZ397POO en C++: Héritage
Redéfinition des opérateurs (suite)Redéfinition des opérateurs (suite)
bool Derived::operator==(const Derived& aDerived) const{
return ( Base::operator==(aDerived) && (ratio_ == aDerived.ratio_)) ? true : false;
}
Derived& Derived::operator=(const Derived& aDerived){
if (this == &aDerived)return *this;
Base::operator=(aDerived);ratio_ = aDerived.ratio_;return *this;
}
Utilisation des opérateurs de la
classe de base dans l'implémentation des
opérateurs de la sous-classe
© 1997-2003 Fabio HERNANDEZ398POO en C++: Héritage
Contrôle d'avancementContrôle d'avancement
MotivationClassificationSpécialisationRedéfinition des fonctions membresConversions standardsContrôle d'accèsClasse abstraiteRedéfinition des opérateursRésumé
© 1997-2003 Fabio HERNANDEZ399POO en C++: Héritage
RésuméRésumé
L'héritage permet de définir de nouvelles classes par extension, spécialisation ou combinaison d'autres déjà définiesUne classe dérivée (ou sous-classe) hérite ses attributs et fonctions membres de la classe de base(ou super-classe)Une sous-classe peut redéfinir les fonctions membres de la classe de baseDes mécanismes de contrôle d'accès permettent aux sous-classes d'accéder aux attributs de la classe de base tout en préservant l'encapsulationUne classe abstraite en est une qui ne peut être utilisée que comme classe de baseLes opérateurs doivent être redéfinis par les sous-classes