Conception d'interfaces en C++
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 61 sur 104.
Une interface en C++ est une classe abstraite où toutes les fonctions membres sont virtuelles pures. Contrairement aux classes abstraites qui peuvent contenir une certaine implémentation, les interfaces définissent uniquement les opérations qui doivent exister - elles spécifient un contrat sans aucun comportement.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};Cette interface Drawable garantit que toute classe d'implémentation aura des méthodes draw() et resize(), mais ne dit rien sur la façon dont elles fonctionnent. Cette séparation est puissante - le code peut dépendre de l'interface sans rien savoir des types concrets.
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};
class Button : public Drawable {
std::string label;
public:
Button(std::string l) : label(l) {}
void draw() override { std::cout << "Drawing button: " << label << std::endl; }
void resize(double factor) override { /* redimensionner le bouton */ }
};Une classe peut implémenter plusieurs interfaces, permettant des conceptions flexibles où les objets peuvent remplir différents rôles :
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};Les interfaces favorisent le couplage faible - votre code dépend d'abstractions plutôt que d'implémentations concrètes, ce qui le rend plus facile à étendre et à tester.
Défi
FacileConstruisons un système de gestion d'appareils qui démontre comment les interfaces définissent des contrats pour différents types d'appareils. Vous allez créer deux interfaces distinctes que les appareils peuvent implémenter, puis construire des classes d'appareils concrètes qui remplissent l'un ou l'autre de ces contrats, ou les deux.
Vous organiserez votre code sur trois fichiers :
Interfaces.h: Définissez deux classes abstraites pures (interfaces) qui représentent différentes capacités d'appareils :Powerable— tout appareil qui peut être allumé et éteint :- Une méthode virtuelle pure
powerOn() - Une méthode virtuelle pure
powerOff() - Une méthode virtuelle pure
getPowerStatus()retournant unstd::string - Un destructeur virtuel
Connectable— tout appareil qui peut se connecter à un réseau :- Une méthode virtuelle pure
connect(const std::string& network) - Une méthode virtuelle pure
disconnect() - Une méthode virtuelle pure
getConnectionInfo()retournant unstd::string - Un destructeur virtuel
- Une méthode virtuelle pure
Devices.h: Implémentez des classes d'appareils concrètes qui utilisent ces interfaces :Lamp— implémente uniquementPowerable:- Un membre privé
bool isOn(commence àfalse) - Un membre privé
std::string name - Un constructeur prenant le nom de la lampe
powerOn()définitisOnà true et affiche :<name>: Light turned onpowerOff()définitisOnà false et affiche :<name>: Light turned offgetPowerStatus()retourne"ON"ou"OFF"selon l'état
SmartTV— implémente à la foisPowerableetConnectable:- Membres privés :
bool isOn(commence à false),std::string brand,std::string currentNetwork(commence vide) - Un constructeur prenant la marque du téléviseur
powerOn()définitisOnà true et affiche :<brand> TV: Powered onpowerOff()définitisOnà false, efface le réseau et affiche :<brand> TV: Powered offgetPowerStatus()retourne"ON"ou"OFF"connect()stocke le nom du réseau et affiche :<brand> TV: Connected to <network>disconnect()efface le réseau et affiche :<brand> TV: DisconnectedgetConnectionInfo()retourne"Connected to <network>"s'il est connecté, ou"Not connected"s'il est vide
- Un membre privé
main.cpp: Lit trois entrées (chacune sur une ligne séparée) :- Nom de la lampe
- Marque de la TV
- Nom du réseau
Créez une
Lampet uneSmartTV. Démontrez comment la même interface peut être utilisée avec différents appareils :Tout d'abord, travaillez avec les deux appareils via l'interface
Powerable. Stockez des pointeurs vers les deux dans un tableau dePowerable*, puis bouclez et appelezpowerOn()sur chacun, suivi de l'affichage de leur statut sous la forme :Status: <powerStatus>Affichez une ligne vide, puis travaillez avec la SmartTV via l'interface
Connectable. Créez un pointeurConnectable*vers votre SmartTV, appelezconnect()avec le nom du réseau, et affichez :Connection: <connectionInfo>Affichez une autre ligne vide, puis éteignez les deux appareils via le tableau
Powerableet affichez leur statut final.
Par exemple, avec les entrées Desk Lamp, Samsung, et HomeWiFi :
Desk Lamp: Light turned on
Status: ON
Samsung TV: Powered on
Status: ON
Samsung TV: Connected to HomeWiFi
Connection: Connected to HomeWiFi
Desk Lamp: Light turned off
Status: OFF
Samsung TV: Powered off
Status: OFFRemarquez comment la SmartTV peut être traitée soit comme un Powerable soit comme un Connectable selon le pointeur d'interface que vous utilisez. Cette flexibilité est la force de l'implémentation de plusieurs interfaces — votre code peut fonctionner avec n'importe quel appareil qui remplit le contrat dont il a besoin, sans connaître le type concret.
Aide-mémoire
Une interface en C++ est une classe abstraite où toutes les fonctions membres sont virtuelles pures. Les interfaces définissent uniquement les opérations qui doivent exister sans aucune implémentation :
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};Les classes implémentent des interfaces en redéfinissant toutes les fonctions virtuelles pures :
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};Une classe peut implémenter plusieurs interfaces en utilisant l'héritage multiple :
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};Les interfaces favorisent le couplage faible en permettant au code de dépendre d'abstractions plutôt que d'implémentations concrètes.
Essayez vous-même
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// Lire les entrées
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Créer un objet Lamp et un objet SmartTV
// TODO: Créer un tableau de pointeurs Powerable* contenant les deux appareils
// TODO: Parcourir le tableau et appeler powerOn() sur chaque appareil
// Après chaque powerOn(), afficher : Status: <powerStatus>
// TODO: Afficher une ligne vide
// TODO: Créer un pointeur Connectable* vers la SmartTV
// Appeler connect() avec le nom du réseau
// Afficher : Connection: <connectionInfo>
// TODO: Afficher une ligne vide
// TODO: Parcourir le tableau Powerable et appeler powerOff() sur chaque appareil
// Après chaque powerOff(), afficher : Status: <powerStatus>
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