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 dont chaque fonction membre—à l’exception du destructeur (virtuel)—est virtuelle pure. 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 qui l’implémente possède les méthodes draw() et resize(), mais ne dit rien sur leur fonctionnement. Cette séparation est puissante : le code peut dépendre de l’interface sans rien connaître 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, ce qui permet 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 un couplage faible : votre code dépend d’abstractions plutôt que d’implémentations concrètes, ce qui facilite son extension et ses tests.
Défi
FacileConstruisons un système de gestion d’appareils qui montre comment les interfaces définissent des contrats pour différents types d’appareils. Vous créerez deux interfaces distinctes que les appareils pourront implémenter, puis vous construirez des classes d’appareils concrètes qui respecteront l’un ou les deux de ces contrats.
Vous organiserez votre code sur trois fichiers :
Interfaces.h: définir deux classes abstraites pures (interfaces) qui représentent différentes capacités d’appareils :Powerable: tout appareil pouvant être allumé et éteint :- Une méthode virtuelle pure
powerOn() - Une méthode virtuelle pure
powerOff() - Une méthode virtuelle pure
getPowerStatus()renvoyant unestd::string - Un destructeur virtuel
Connectable: tout appareil pouvant 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()renvoyant unestd::string - Un destructeur virtuel
- Une méthode virtuelle pure
Devices.h: implémenter des classes d’appareils concrètes qui utilisent ces interfaces :Lamp: implémente uniquementPowerable:- Un membre privé
bool isOn(initialisé àfalse) - Un membre privé
std::string name - Un constructeur prenant le nom de la lampe
powerOn()affecte la valeur true àisOnet affiche :<name>: Light turned onpowerOff()affecte la valeur false àisOnet affiche :<name>: Light turned offgetPowerStatus()renvoie"ON"ou"OFF"selon l’état
SmartTV: implémente à la foisPowerableetConnectable:- Membres privés :
bool isOn(initialisé à false),std::string brand,std::string currentNetwork(initialisé à une chaîne vide) - Un constructeur prenant la marque du téléviseur
powerOn()affecte la valeur true àisOnet affiche :<brand> TV: Powered onpowerOff()affecte la valeur false àisOn, efface le réseau et affiche :<brand> TV: Powered offgetPowerStatus()renvoie"ON"ou"OFF"connect()enregistre le nom du réseau et affiche :<brand> TV: Connected to <network>disconnect()efface le réseau et affiche :<brand> TV: DisconnectedgetConnectionInfo()renvoie"Connected to <network>"si l’appareil est connecté, ou"Not connected"si le réseau est vide
- Un membre privé
main.cpp: lire trois entrées (chacune sur une ligne distincte) :- Nom de la lampe
- Marque du téléviseur
- Nom du réseau
Créer une
Lampet unSmartTV. Montrer comment une même interface peut être utilisée avec différents appareils :Commencer par travailler avec les deux appareils via l’interface
Powerable. Stocker des pointeurs vers les deux appareils dans un tableau dePowerable*, puis parcourir ce tableau et appelerpowerOn()sur chaque appareil, avant d’afficher leur état sous la forme :Status: <powerStatus>Afficher une ligne vide, puis travailler avec le SmartTV via l’interface
Connectable. Créer un pointeurConnectable*vers votre SmartTV, appelerconnect()avec le nom du réseau, puis afficher :Connection: <connectionInfo>Afficher une autre ligne vide, puis éteindre les deux appareils via le tableau
Powerableet afficher leur état 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 le SmartTV peut être traité comme un Powerable ou comme un Connectable, selon le pointeur d’interface que vous utilisez. Cette flexibilité constitue l’intérêt d’implémenter plusieurs interfaces. Votre code peut fonctionner avec tout appareil qui respecte le contrat dont il a besoin, sans connaître son type concret.
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 le SmartTV
// Appeler connect() avec le nom du réseau
// Print: 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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne