Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Langage C++
  3. Classe abstraite et interface
Extrait - Langage C++ De l'héritage C au C++ moderne (avec programmes d'illustration) (2e édition)
Extraits du livre
Langage C++ De l'héritage C au C++ moderne (avec programmes d'illustration) (2e édition) Revenir à la page d'achat du livre

Classe abstraite et interface

Classe abstraite, fonctions virtuelles pures

Dans l’exemple donné avec la classe Sport, la fonction virtuelle de la classe de base est uniquement prétexte à des redéfinitions dans les classes dérivées et ses instructions dans la classe de base ne servent pas. Finalement, cette classe de base sert uniquement à dériver des classes filles et elle n’est pas utilisée autrement. Il n’y a pas d’objet de la classe Sport. En quelque sorte, dans notre programme, un sport tout court n’existe pas : ce sera du judo ou du tennis ou de la natation, etc. C’est le principe d’une classe abstraite. La classe Sport remplit les conditions pour être implémentée comme une classe abstraite. Voici les caractéristiques d’une classe abstraite :

  • Une classe abstraite est une classe de base qui servira uniquement à dériver des classes complémentaires.

  • Elle ne permet pas d’obtenir des objets.

  • Elle contient des fonctions virtuelles dites "pures", c’est-à-dire uniquement les déclarations des fonctions, mais pas leurs définitions. Les définitions sont faites dans chacune des classes dérivées.

  • La déclaration d’une fonction virtuelle pure se fait en ajoutant = 0 après. Par exemple : virtual void affiche() = 0;

  • Pour obtenir une classe abstraite, il suffit de déclarer une fonction virtuelle pure à l’intérieur.

À partir de l’exemple précédent, nous pouvons facilement transformer la classe de base Sport en une classe abstraite :

#include <iostream> 
#include <vector> 
#include <memory> 
 
class Sport 
{ 
public: 
    virtual ~Sport() { std::cout << "Destructeur Sport\n"; } 
 
    //...

Tronc commun pour dériver

Dans la perspective d’utiliser le polymorphisme pour contrôler des objets différents à partir d’un tronc commun de fonctions ayant les mêmes noms, l’intérêt d’une classe abstraite est de normaliser et regrouper ces contrôles. Éventuellement, des variables peuvent être associées au tronc commun des fonctions dans la classe de base. Les fonctions virtuelles pures constituent l’interface des contrôles communs à toutes les classes dérivées. Voici à titre illustratif l’amorce d’un programme qui ouvre sur un éventail de formes géométriques :

#include <iostream> 
#include <ctime> 
#include <vector> 
#include <memory> 
 
class Graph 
{ 
protected: 
    int x1, y1, color; 
    char lettre; 
    Graph() : x1{ rand() % 100 }, y1{ rand() % 100 }, 
        color{ rand() % 15 + 1 }, lettre{ rand() % 26 + 'A' } {} 
 
public: 
    virtual void dessine() = 0; 
    virtual void deplace() = 0; 
}; 
/***************************/ 
class Point : public Graph 
{ 
public: 
    Point() : Graph() {} 
    void dessine(); 
    void deplace(); 
}; 
void Point::dessine() 
{ 
    std::cout << "POINT : \n"; 
    std::cout << "\tx1 : " << x1 << '\n'; 
    std::cout << "\ty1 : " << y1 << '\n'; ...

Interface

Une interface est une classe abstraite utilisée uniquement pour désigner un ensemble de fonctionnalités sans les implémenter ; c’est-à-dire des fonctions virtuelles pures, qui seront définies seulement dans les classes dérivées. Par exemple, admettons qu’un système d’exploitation ait besoin, pour gérer ses périphériques, des mêmes actions mais selon des pilotes différents réclamant chacun une implémentation spécifique de ces actions. L’ensemble des actions à effectuer pourrait constituer une interface sous la forme d’une classe abstraite :

class GestionPilote 
{ 
public : 
    virtual int ouvrir( int option) =0; 
    virtual int fermer(int option) =0; 
    virtual int lire(char*p, int n) =0; 
    virtual int ecrire(char*p, int n)=0; 
    virtual int iocntl(int ...)=0; 
    virtual ~GestionPilote(){}  // destructeur virtuel 
}; 

L’ensemble des actions à effectuer est répertorié par la classe abstraite, mais l’implémentation de chacune dépendra des pilotes et des périphériques concernés.

class GestionLeapMotion: public GestionPilote 
{ 
    /* propriétés requises*/ 
 
    /* et implémentation spécifique des fonctionnalités 
       annoncées dans la classe GestionPilote*/ 
} 
 
class GestionKinect: public GestionPilote 
{ 
    /* propriétés requises*/ 
 
    /* et implémentation spécifique...

Récupérer une sous classe depuis une base abstraite

Si nécessaire, un moyen simple nous permet d’accéder à toutes les propriétés ou fonctions publiques non virtuelles d’une classe dérivée depuis sa base abstraite. 

Pour ce faire, il suffit d’identifier dans un enum les différents types de sous-classes :

#include <iostream> 
#include <ctime> 
#include <vector> 
 
enum TypeEnnemi { TROLL, GOBELIN /*...autres*/ }; 

Ensuite dans la classe abstraite de base, une fonction virtuelle pure sert à remonter pour chaque sous-classe son type (Troll, Gobelin, etc.) :

class Ennemi 
{ 
public: 
    virtual void Avancer() = 0; 
    virtual void Combattre() = 0; 
 
    // pour identifier la sous-classe pointée 
    virtual TypeEnnemi GetType() = 0; 
}; 

La classe Troll est implémentée dans le prolongement de la façon suivante :

class Troll : public Ennemi 
{ 
private: 
    int massue{ 0 }; 
public: 
    Troll() = default; 
    void Avancer() { std::cout << "troll avance\n"; } 
    void Combattre()  
    {  
        std::cout << "Troll combat, massue " << massue << '\n';  
    } 
 
    TypeEnnemi GetType() { return TypeEnnemi::TROLL; } 
 
    // non virtuuelle 
    int& GetMassue() { return massue; } 
 
}; 

La fonction GetType retourne simplement le type...

Résumé classe abstraite et interface

En résumé, une classe est abstraite lorsqu’elle désigne un type d’objet qui ne sera jamais présent en lui-même dans le programme mais qui sert uniquement de base et de tronc commun d’éléments pour la création d’un ensemble de types d’objet dérivés. Parler de classe abstraite suppose ainsi toujours la gestion de plusieurs héritages.

Soit, par exemple, un programme qui prend en compte les comportements différents entre fourmis rouges carnivores et fourmis noires non carnivores. Les deux types de fourmis possèdent un ensemble commun de caractéristiques et de comportements (elles se déplacent, elles mangent, elles se battent…). Cependant, elles ne réalisent pas ces mêmes actions de façon identique.

Nous pouvons concevoir une classe de base fourmi à partir de laquelle nous allons dériver deux types de fourmis, les rouges et les noires. Dans le programme, nous utiliserons uniquement des fourmis noires ou des fourmis rouges, mais il n’y aura jamais de fourmi tout court.

La classe fourmi est pour cette raison une classe abstraite. Elle sert uniquement de base à la définition des fourmis rouges et des fourmis noires qui seules donneront lieu à des objets concrets.

Les comportements et actions présents dans la classe fourmi de base, que l’on retrouve sous forme de fonctions chez toutes les fourmis, constituent un ensemble d’actions commun à toutes les fourmis. Toutes se déplacent, mangent, se battent, cherchent de la nourriture, etc., mais ces actions peuvent être exécutées différemment, et ces fonctions se redéfinissent dans les classes dérivées.

Ce tronc commun d’actions forme ce que l’on appelle une interface :...

Expérimentation : exemples des super-héros, les Avengers

Dans l’expérimentation ci-dessous, nous utilisons des conteneurs C++. Une introduction à l’utilisation des conteneurs est donnée dans la troisième partie du livre au chapitre Tableaux statiques, introduction conteneurs dans la section Introduction des conteneurs.

1. Classe de base super-héros, interface des fonctions d’action

Un super-héros porte un nom, il possède des armes et une identité de caractère. 

Chaque arme a son propre type, une portée et une force.

Le caractère d’un super-héros résulte d’un paramétrage émotionnel personnalisé.

a. Définition et stockage des armes

Une structure suffit à implémenter une arme. Elle contient une chaîne de caractères pour le type, un int pour la force et un autre pour la portée :

#include <iostream> 
#include <string> 
#include <array> 
 
struct arme 
{ 
    std::string type; 
    int force, portee; 
    arme(); 
}; 
arme::arme() 
{ 
    // déclaration tableau de 6 strings  
    std::array<std::string, 6> s = { "fusilmit","revolver","arc", 
                                "poignard","MMA","muscles" }; 
    // la méthode size() retourne le nombre d'éléments du tableau 
    type = s[rand() % s.size()]; 
    force = rand()...