LOCODUINO

Aide
Forum de discussion
Dépôt GIT Locoduino
Flux RSS

mardi 19 mars 2024

Visiteurs connectés : 40

Le monde des objets (3)

Une sombre histoire d’héritage...

.
Par : Thierry

DIFFICULTÉ :

Attention : ce chapitre est long, ardu et demande de la concentration.
Ha ? Vous êtes encore là ? Alors lisez lentement, n’hésitez pas à revenir sur vos pas. Chaque mot est important, en particulier dans les lignes de code. Laissez les principes entrer et faire leur chemin, quitte à en rester là pour le moment et revenir plus tard reprendre la lecture...

Nous avons parlé des classes et de leur capacité à rendre un code souple simple à créer et à maintenir. Voyons maintenant les avantages du paradigme objet qui l’ont rendu incontournable.

<

L’héritage sans soucis

Depuis le deuxième article, nous avons vu une jolie classe Led. Mais comment ferions nous pour coder une classe LedBicouleur qui s’occuperait d’une diode à deux couleurs dotée de trois pins ? Ce type de diode est le plus souvent un assemblage de deux leds simples dans un seul boitier que l’on peut activer seules ou ensembles. Seule la cathode est partagée.

La première idée, c’est du simple copié/collé avec une petite adaptation.

#define HIGH2   2       // Pour allumer la pin2
#define HIGH12  3       // Pour allumer les deux pins !
 
class LedBicouleur 
{
private:
  byte pin1;
  byte pin2;
  byte etat;  // HIGH, LOW, HIGH2 et même HIGH12 !
  bool montageInverse;
  void Rafraichir();
  
public:
  LedBicouleur (byte aPin1, byte apin2, aMontageInverse = false);
  void Setup();
  void Allumer();
  void Allumer2();
  void Allumer12();
  void Eteindre();
};

Qu’est ce qui a changé ici ? Une seconde broche pin2 a été ajoutée. La variable etat peut prendre quatre valeurs. HIGH, LOW, et deux inventions locales, HIGH2 pour allumer la pin 2, HIGH12 pour tout allumer ! Le constructeur reflète cette nouvelle organisation en ajoutant la seconde broche. Enfin, les méthodes Allumer2() et Allumer12() ont été ajoutées. Allumer() allume la première diode, Allumer2() la seconde, Allumer12() allume les deux, et Eteindre() éteint le tout.

On sent bien que c’est quand même très proche de la classe Led vue dans l’article précédent... Nous allons exploiter une nouvelle et très importante notion du C++ : l’héritage (on parle aussi de dérivation). Ce principe permet de dire qu’un objet est une sorte de un autre objet... Ici, la led bi-couleur est bien une sorte de led. On parle également de dérivation LedBicouleur serait la classe dérivée de Led. Voici comment exprimer l’héritage en code C++ :

class Led // l'originale, la classe de base
{
...
};
 
class LedBicouleur : public Led // LedBicouleur dérive de Led
{
...
};

LedBicouleur hérite ou dérive de Led. Le mot clé public devant le nom de la classe de base, celle dont dérive LedBicouleur, est là pour lui permettre de voir le contenu de sa classe de base Led, en tout cas la partie que Led a jugé bon de ne pas déclarer privé.

Ce qui est fait, est fait !

Mais pourquoi faire une classe dérivée ? Est ce que la classe LedBicouleur ne pouvait pas exister sans dérivation ? Si, bien sûr, mais l’héritage permet de donner des comportements communs à un ensemble de classes, de capitaliser sur ce qui est déjà fait et fiabilisé, de diminuer le volume de code tout en rationalisant le fonctionnement et la maintenance.

class LedBicouleur : public Led
{
private
  byte pin2;
 
public:
  LedBicouleur(byte aPin1, byte aPin2, bool aMontageInverse = false);
  void Allumer2();
  void Allumer12();
};

Cette led particulière ayant deux pins différentes à piloter, il faut en ajouter une à celle déjà présente dans la classe de base, c’est pin2, et l’initialiser dans le constructeur :

LedBicouleur::LedBicouleur(byte aPin1, byte aPin2, bool aMontageInverse) : Led(aPin1, aMontageInverse)
{
  pin2 = aPin2;
  pinMode(pin2, OUTPUT);
 
  Eteindre();
}

Le C++ permet de faire appel au constructeur de sa classe de base Led en lui redonnant les bons arguments, et se contente de ne faire que la partie supplémentaire autour de pin2.

Pourtant ce constructeur ne va pas fonctionner correctement. Voyez vous ce qui ne va pas ? Eteindre() n’est présent que dans Led, et pas dans LedBicouleur, ce qui fait que cette méthode ne connait pas pin2 et donc n’éteindra pas cette broche... Nous devons faire en sorte qu’Eteindre() marche pour les deux classes.

Dans le code de la méthode Led::Eteindre(), on a :

void Led::Eteindre()
[
  etat = LOW;
  Rafraichir();
}

Dans la nouvelle classe, on a dit dès le départ que etat = LOW éteindrait les deux broches. C’est donc Rafraichir() qui doit faire ce travail. Mais cette méthode existe déjà dans Led, comment en faire une nouvelle dans LedBicouleur qui mette à jour les deux broches ? Pour cela, il y a la surcharge de méthode.

class Led
{
private:
  byte pin;
  byte etat;
  bool montageInverse;
  virtual void Rafraichir();
 
public:
  Led(byte aPin, bool aMontageInverse = false);
  void Allumer();
  void Eteindre();
};

class LedBicouleur : public Led
{
private:
  byte pin2;
  void Rafraichir();
 
public:
  LedBicouleur(byte aPin1, byte aPin2, bool aMontageInverse = false);
  void Allumer2();
};

Dans la classe Led, est apparu le mot clé virtual qui déclare la fonction Rafraichir() virtuelle. Ce terme désigne une fonction qui dépend du type réel de la classe qui l’applique. Si c’est une Led, on utilisera Led::Rafraichir(), si c’est une LebBicouleur on utilisera LedBicouleur::Rafraichir() si elle existe, sinon on retrouvera la méthode de sa classe de base : Led::Rafraichir(). En effet, virtual ouvre une porte et permet aux classes dérivées de fournir un comportement alternatif, mais ce n’est pas une obligation ! Dans LedBicouleur, j’ai effectivement redéfini, surchargé Rafraichir(). Attention, le nom, le type de retour et les arguments doivent être exactement les mêmes, sinon le compilateur considérera qu’il s’agit d’une autre méthode, mais sans prévenir. Codons la nouvelle fonction :

void LedBicouleur::Rafraichir()
{
  byte etatLed1 = LOW;
  byte etatLed2 = LOW;
 
  switch(etat)
  {
    case HIGH:
      etatLed1 = HIGH;
      break;
      
    case HIGH2:
      etatLed2 = HIGH;
      break;
      
    case HIGH12:
      etatLed1 = HIGH;
      etatLed2 = HIGH;
      break;
  }
 
  if (montageInverse == true)  // si c'est un montage inversé
  {
    etatLed1 = ! etatLed1; // on inverse
    etatLed2 = ! etatLed2; // on inverse
  }
 
  digitalWrite(pin, etatLed1);
  digitalWrite(pin2, etatLed2);
}

Dans cette fonction, on crée deux états etatLed1 et etatLed2, un pour chaque couleur de la led. Les deux sont initialisés à LOW. Puis avec le switch, chaque état est mis à HIGH en fonction de ce qui est demandé. On inverse si nécessaire en fonction du flag montageInverse, et on fini par appliquer l’état sur chaque led. Cette fonction remaniée va nous permettre de coder Allumer2() et Allumer12() assez facilement :

void LedBicouleur::Allumer2()
{
  etat = HIGH2;
  Rafraichir();
}

void LedBicouleur::Allumer12()
{
  etat = HIGH12;
  Rafraichir();
}

Et là, nouveau problème. Les données pin et etat de la classe Led sont privées (private). LedBicouleur n’a pas le droit de les lire et encore moins de les changer ! Pour que ce code puisse fonctionner, elles peuvent rester cachées au yeux de tous, mais doivent devenir visibles pour les classes dérivées. C’est le rôle du modificateur protected (protégé) qui remplace private et ne laisse les données et les fonctions visibles que pour les classes dérivées.

class Led
{
protected:
  byte pin;
  byte etat;
  bool montageInverse;
  virtual void Rafraichir();
 
public:
  Led(byte aPin, bool aMontageInverse);
  void Allumer();
  void Eteindre();
};

Utiliser cette nouvelle classe est à peine plus compliqué qu’une led simple :

LedBicouleur maLed(10, 11);

void setup()
{
  maLed.Setup();
}

void loop()
{
  maLed.Allumer();
  delay(1000);
  maled.Eteindre();
  maLed.Allumer2();
  delay(1000);
  maLed.Eteindre();
  delay(1000);
}

Les plus perspicaces d’entre vous auront remarqué que, pour l’instant, rien ne justifie l’emploi de l’héritage par rapport à un objet LedBicouleur qui contiendrait brutalement deux Led ! C’est d’ailleurs un excellent exercice que je vous enjoins de coder pour le plaisir...

Nous verrons dans le prochain article quel immenses avantages l’on peut retirer de l’héritage comme méthode de programmation, plutôt que la force brute...

5 Messages

Réagissez à « Le monde des objets (3) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Programmation »

Les pointeurs (1)

Les pointeurs (2)

Les Timers (I)

Les Timers (II)

Les Timers (III)

Les Timers (IV)

Les Timers (V)

Bien utiliser l’IDE d’Arduino (1)

Bien utiliser l’IDE d’Arduino (2)

Piloter son Arduino avec son navigateur web et Node.js (1)

Piloter son Arduino avec son navigateur web et Node.js (2)

Piloter son Arduino avec son navigateur web et Node.js (3)

Piloter son Arduino avec son navigateur web et Node.js (4)

Comment gérer le temps dans un programme ?

La programmation, qu’est ce que c’est

Types, constantes et variables

Installation de l’IDE Arduino

Répéter des instructions : les boucles

Les interruptions (1)

Instructions conditionnelles : le if ... else

Instructions conditionnelles : le switch ... case

Comment concevoir rationnellement votre système

Comment gérer l’aléatoire ?

Calculer avec l’Arduino (1)

Calculer avec l’Arduino (2)

Les structures

Systèmes de numération

Les fonctions

Trois façons de déclarer des constantes

Transcription d’un programme simple en programmation objet

Ces tableaux qui peuvent nous simplifier le développement Arduino

Les chaînes de caractères

Trucs, astuces et choses à ne pas faire !

Processing pour nos trains

Arduino : toute première fois !

Démarrer en Processing (1)

TCOs en Processing (1)

TCOs en Processing (2)

L’assembleur (1)

L’assembleur (2)

L’assembleur (3)

L’assembleur (4)

L’assembleur (5)

L’assembleur (6)

L’assembleur (7)

L’assembleur (8)

L’assembleur (9)

Les derniers articles

L’assembleur (9)


Christian

L’assembleur (8)


Christian

L’assembleur (7)


Christian

L’assembleur (6)


Christian

L’assembleur (5)


Christian

L’assembleur (4)


Christian

L’assembleur (3)


Christian

L’assembleur (2)


Christian

L’assembleur (1)


Christian

TCOs en Processing (2)


Pierre59

Les articles les plus lus

Les Timers (I)

Les interruptions (1)

Instructions conditionnelles : le if ... else

Bien utiliser l’IDE d’Arduino (1)

Ces tableaux qui peuvent nous simplifier le développement Arduino

Comment gérer le temps dans un programme ?

Les structures

Les Timers (III)

Les Timers (II)

Instructions conditionnelles : le switch ... case