L’abstraction
Dans le contexte de l’héritage, l’abstraction joue un rôle crucial pour concevoir des systèmes modulaires et flexibles. Privilégiez la conception d’interfaces avant de penser aux implémentations concrètes. En vous concentrant d’abord sur les abstractions, vous pourrez définir les relations et les interactions entre les différentes parties de votre système sans vous préoccuper des détails spécifiques de l’implémentation.
Lors de la conception de votre application, basez-vous sur des abstractions plutôt que sur des classes concrètes. En faisant cela, vous permettez à votre code d’être plus générique, réutilisable et évolutif. Les abstractions servent de base pour définir les comportements attendus des classes qui en hériteront, sans imposer une implémentation spécifique. Cela favorise la création de systèmes plus flexibles et évolutifs.
L’abstraction d’une classe empêche son instanciation directe, mais constitue une excellente manière de créer une interface publique pour les classes dérivées. En définissant une classe abstraite, vous établissez un cadre pour les classes dérivées, tout en leur laissant la liberté d’implémenter leurs propres variantes de comportement.
De plus, l’abstraction d’une méthode oblige le développeur à spécialiser et à implémenter cette méthode dans les classes dérivées. Cela garantit que les classes héritant d’une classe abstraite répondent aux exigences de l’interface définie par la classe de base. En implémentant les méthodes abstraites, vous vous assurez que chaque classe dérivée respecte les règles établies par la classe abstraite, tout en conservant la possibilité de personnaliser les implémentations.
Comment créer une classe abstraite ?
Pour créer une classe abstraite, il est important de définir une structure de base qui représente les propriétés et méthodes communes aux classes dérivées. Une classe abstraite sert de modèle pour les classes dérivées, en établissant les caractéristiques essentielles que toutes les classes dérivées doivent posséder. Bien qu’une classe abstraite ne puisse pas être instanciée directement, elle peut contenir des méthodes avec un comportement par défaut, que les classes dérivées pourront utiliser ou redéfinir si nécessaire.
Reprenons l’exemple des classes « Véhicule », « Auto » et « Moto ». Dans ce cas, nous définissons « Véhicule » comme une classe abstraite, car un véhicule doit forcément être une « Auto » ou une « Moto » et ne peut pas être instancié en tant que véhicule générique. La classe « Véhicule » peut contenir des propriétés communes à toutes les classes dérivées, telles que la couleur, le poids ou la vitesse maximale. De plus, elle peut également inclure des méthodes avec un comportement par défaut, comme la méthode démarrer()
.
from abc import ABC, abstractmethod class Vehicule(ABC): def __init__(self, couleur): self.couleur = couleur self.moteur_allume = False def demarrer(self): self.moteur_allume = True class Voiture(Vehicule): def __init__(self, couleur, nb_portes): super().__init__(couleur) self.nb_portes = nb_portes class Moto(Vehicule): def __init__(self, couleur): super().__init__(couleur) self.coupe_circuit = True def demarrer(self): if not self.coupe_circuit: super().demarrer()
#include <iostream> #include <string> class Vehicule { public: Vehicule(std::string couleur) : couleur(couleur), moteur_allume(false) {} virtual void demarrer() { moteur_allume = true; } protected: std::string couleur; bool moteur_allume; }; class Voiture : public Vehicule { public: Voiture(std::string couleur, int nb_portes) : Vehicule(couleur), nb_portes(nb_portes) {} private: int nb_portes; }; class Moto : public Vehicule { public: Moto(std::string couleur) : Vehicule(couleur), coupe_circuit(true) {} void demarrer() override { if (!coupe_circuit) { Vehicule::demarrer(); } } private: bool coupe_circuit; };
using System; public abstract class Vehicule { public string Couleur { get; set; } public bool MoteurAllume { get; set; } public Vehicule(string couleur) { Couleur = couleur; MoteurAllume = false; } public virtual void Demarrer() { MoteurAllume = true; } } public class Voiture : Vehicule { public int NbPortes { get; set; } public Voiture(string couleur, int nbPortes) : base(couleur) { NbPortes = nbPortes; } } public class Moto : Vehicule { public bool CoupeCircuit { get; set; } public Moto(string couleur) : base(couleur) { CoupeCircuit = true; } public override void Demarrer() { if (!CoupeCircuit) { base.Demarrer(); } } }
Comment créer une méthode abstraite ?
Pour créer une méthode abstraite, il faut définir une signature de méthode sans implémentation au sein de la classe abstraite. Cette méthode abstraite sert alors de contrat que les classes dérivées doivent respecter en fournissant leur propre implémentation. Une méthode abstraite est essentiellement une promesse qu’une certaine fonctionnalité sera disponible dans les classes dérivées, mais sans imposer une implémentation spécifique.
Reprenons l’exemple des classes « Véhicule », « Auto » et « Moto ». Imaginons que nous voulions que chaque classe spécialisée fournisse sa propre implémentation pour la méthode démarrer()
. Dans ce cas, nous pouvons définir une méthode abstraite démarrer()
dans la classe « Véhicule ». Les classes « Auto » et « Moto » hériteront de cette méthode abstraite et devront fournir leur propre implémentation pour respecter le contrat établi par la classe « Véhicule ».
from abc import ABC, abstractmethod class Vehicule(ABC): def __init__(self, couleur): self.couleur = couleur self.moteur_allume = False @abstractmethod def demarrer(self): pass class Voiture(Vehicule): def __init__(self, couleur, nb_portes): super().__init__(couleur) self.nb_portes = nb_portes def demarrer(self): self.moteur_allume = True class Moto(Vehicule): def __init__(self, couleur): super().__init__(couleur) self.coupe_circuit = True def demarrer(self): if not self.coupe_circuit: self.moteur_allume = True
#include <iostream> #include <string> class Vehicule { public: Vehicule(std::string couleur) : couleur(couleur), moteur_allume(false) {} virtual ~Vehicule() {} virtual void demarrer() = 0; protected: std::string couleur; bool moteur_allume; }; class Voiture : public Vehicule { public: Voiture(std::string couleur, int nb_portes) : Vehicule(couleur), nb_portes(nb_portes) {} void demarrer() override { moteur_allume = true; } private: int nb_portes; }; class Moto : public Vehicule { public: Moto(std::string couleur) : Vehicule(couleur), coupe_circuit(true) {} void demarrer() override { if (!coupe_circuit) { moteur_allume = true; } } private: bool coupe_circuit; };
using System; public abstract class Vehicule { public string Couleur { get; } protected bool MoteurAllume { get; set; } protected Vehicule(string couleur) { Couleur = couleur; MoteurAllume = false; } public abstract void Demarrer(); } public class Voiture : Vehicule { public int NbPortes { get; } public Voiture(string couleur, int nbPortes) : base(couleur) { NbPortes = nbPortes; } public override void Demarrer() { MoteurAllume = true; } } public class Moto : Vehicule { private bool CoupeCircuit { get; set; } public Moto(string couleur) : base(couleur) { CoupeCircuit = true; } public override void Demarrer() { if (!CoupeCircuit) { MoteurAllume = true; } } }
Il est important de noter qu’une classe abstraite peut contenir des méthodes avec un comportement par défaut ainsi que des méthodes abstraites. Si toutes les méthodes d’une classe abstraite sont abstraites, on parle alors d’interfaces. Les interfaces servent à définir un ensemble de comportements attendus sans fournir aucune implémentation, laissant la responsabilité aux classes dérivées de les implémenter selon leurs besoins spécifiques.