Redéfinition de méthodes
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 51 sur 104.
La redéfinition de méthode se produit lorsqu'une classe dérivée fournit sa propre implémentation d'une méthode qui existe déjà dans la classe de base. Cela permet aux classes dérivées de personnaliser le comportement hérité tout en conservant la même signature de méthode.
Pour redéfinir une méthode, définissez simplement une méthode dans la classe dérivée avec exactement le même nom, le même type de retour et les mêmes paramètres :
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // Sortie : Woof!La version de la classe dérivée masque la version de la classe de base. Cependant, vous pouvez toujours accéder à la méthode de la classe de base en utilisant l'opérateur de résolution de portée :
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Appeler la version de la classe de base
std::cout << "Woof!" << std::endl;
}
};Il existe une limitation importante avec cette forme de base de redéfinition (overriding). Lorsque vous accédez à un objet dérivé via un pointeur ou une référence de classe de base, c'est la version de la classe de base qui est appelée à la place :
Dog d;
Animal* ptr = &d;
ptr->speak(); // Résultat : Some sound (pas Woof !)Cela se produit parce que le compilateur détermine quelle méthode appeler en se basant sur le type du pointeur, et non sur le type réel de l'objet. Pour parvenir à un véritable polymorphisme d'exécution où la méthode correcte est appelée en fonction de l'objet réel, vous aurez besoin de fonctions virtuelles, que nous aborderons dans la prochaine leçon.
Défi
FacileConstruisons un système de notification qui démontre la redéfinition de méthode (method overriding) en action. Vous allez créer une classe de base Notifier et deux notificateurs spécialisés qui personnalisent la manière dont les messages sont délivrés.
Vous organiserez votre code sur trois fichiers :
Notifier.h: Définissez une classe de baseNotifieravec :- Un membre protégé
std::string recipient - Un constructeur qui prend un nom de destinataire et le stocke
- Une méthode publique
send(const std::string& message)qui affiche :Sending to <recipient>: <message>
- Un membre protégé
EmailNotifier.h: Définissez une classeEmailNotifierqui hérite publiquement deNotifier:- Un constructeur qui prend un destinataire et le transmet à la classe de base
- Redéfinissez la méthode
send()pour appeler d'abord la version de la classe de base en utilisantNotifier::send(), puis affichez :[Email sent successfully]
main.cpp: Lisez deux entrées (chacune sur une ligne séparée) :- Le nom du destinataire (chaîne de caractères)
- Le contenu du message (chaîne de caractères)
Définissez une classe
SMSNotifierdirectement dans main.cpp qui hérite publiquement deNotifier:- Un constructeur qui prend un destinataire et le transmet à la classe de base
- Redéfinissez la méthode
send()pour afficher :SMS to <recipient>: <message>(en remplaçant complètement le comportement de base sans l'appeler)
Créez un objet de chaque type de notificateur (base
Notifier,EmailNotifier, etSMSNotifier) en utilisant le même destinataire. Appelezsend()sur chacun avec le message d'entrée, en affichant une ligne de séparation entre chaque :--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
Par exemple, avec les entrées Alice et Hello there! :
--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!Remarquez comment EmailNotifier étend le comportement de base en appelant d'abord la méthode parente, tandis que SMSNotifier le remplace complètement par sa propre implémentation. N'oubliez pas les gardes d'inclusion (header guards) et d'inclure les en-têtes appropriés dans chaque fichier.
Aide-mémoire
La redéfinition de méthode (method overriding) permet à une classe dérivée de fournir sa propre implémentation d'une méthode de la classe de base, tout en conservant la même signature de méthode (nom, type de retour et paramètres).
Syntaxe de base de la redéfinition :
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // Output: Woof!La méthode de la classe dérivée masque la version de la classe de base. Pour appeler la méthode de la classe de base depuis la classe dérivée, utilisez l'opérateur de résolution de portée :
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Call base class version
std::cout << "Woof!" << std::endl;
}
};Limitation importante : Lors de l'accès à un objet dérivé via un pointeur ou une référence de la classe de base, c'est la version de la classe de base qui est appelée :
Dog d;
Animal* ptr = &d;
ptr->speak(); // Output: Some sound (not Woof!)Ceci est dû au fait que le compilateur détermine quelle méthode appeler en fonction du type de pointeur, et non du type réel de l'objet. Pour un véritable polymorphisme au moment de l'exécution, utilisez des fonctions virtuelles.
Essayez vous-même
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
// TODO: Définir la classe SMSNotifier ici qui hérite publiquement de Notifier
// - Constructeur qui passe recipient à la classe de base
// - Surcharger send() pour afficher : SMS to <recipient>: <message>
// (remplacer complètement le comportement de base, ne pas appeler le send() parent)
int main() {
string recipient;
string message;
getline(cin, recipient);
getline(cin, message);
// TODO: Créer un objet Notifier avec recipient
// Afficher "--- Base Notifier ---"
// Appeler send() avec message
// TODO: Créer un objet EmailNotifier avec recipient
// Afficher "--- Email Notifier ---"
// Appeler send() avec message
// TODO: Créer un objet SMSNotifier avec recipient
// Afficher "--- SMS Notifier ---"
// Appeler send() avec message
return 0;
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique