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...