Les 4 Piliers de la POO
Encapsulation, Héritage, Polymorphisme, Abstraction. Les fondations du Clean Code !
L'héritage en action
Clique sur une classe-fille (Chien ou Chat). Les membres définis dans la classe-mère Animal remontent le long du lien : la fille en hérite automatiquement.
Choisis une classe pour voir ce qu'elle hérite.
Vue d'ensemble : Les piliers de la POO
La Programmation Orientée Objet repose sur 4 piliers fondamentaux. Ces concepts sont essentiels pour écrire du Clean Code et sont utilisés dans tous les projets professionnels.
💼 Maîtriser ces concepts = code maintenable, réutilisable et professionnel.
🎯 Objectif Clean Code : Ces principes permettent de créer du code modulaire, testable et évolutif. C'est ce qui différencie un code amateur d'un code professionnel !
Encapsulation
L'encapsulation consiste à cacher les détails internes d'un objet et à contrôler l'accès aux données via des méthodes.
🔐 C'est comme un coffre-fort : on ne peut pas accéder directement au contenu, il faut passer par une interface (le code).
🔒 Modificateurs de visibilité
public
protected
private
class CompteBancaire: def __init__(self, solde): self.__solde = solde # __ = privé # Getter - lire la valeur def get_solde(self): return self.__solde # Setter - modifier avec validation def deposer(self, montant): if montant > 0: self.__solde += montant compte = CompteBancaire(1000) # compte.__solde = -999 ❌ Impossible ! compte.deposer(500) # ✅ Passe par la méthode
public class CompteBancaire { private double solde; // Privé ! public CompteBancaire(double solde) { this.solde = solde; } // Getter public double getSolde() { return solde; } // Setter avec validation public void deposer(double montant) { if (montant > 0) { solde += montant; } } }
💡 Pourquoi ? On évite qu'un autre développeur modifie
directement solde = -1000000. Les getters/setters permettent
de valider les données !
L'Héritage
L'héritage permet à une classe enfant de récupérer les attributs et méthodes d'une classe parent.
C'est comme dans la vraie vie : un enfant hérite des caractéristiques de ses parents !
Arbre d'heritage
💡 Tous heritent : Les trois ont automatiquement nom, age et manger() du parent Animal !
Syntaxe de l'heritage
# Classe parent class Animal: def __init__(self, nom): self.nom = nom def manger(self): return f"{self.nom} mange." # Classe enfant (herite de Animal) class Chien(Animal): # ← Parentheses = heritage def __init__(self, nom, race): super().__init__(nom) # Appelle le parent self.race = race def aboyer(self): return "Wouf wouf !" rex = Chien("Rex", "Berger") print(rex.manger()) # "Rex mange." (herite) print(rex.aboyer()) # "Wouf wouf !" (propre)
// Classe parent class Animal { constructor(nom) { this.nom = nom; } manger() { return `${this.nom} mange.`; } } // Classe enfant (herite de Animal) class Chien extends Animal { // ← extends = heritage constructor(nom, race) { super(nom); // Appelle le parent this.race = race; } aboyer() { return "Wouf wouf !"; } } const rex = new Chien("Rex", "Berger"); console.log(rex.manger()); // "Rex mange." (herite) console.log(rex.aboyer()); // "Wouf wouf !" (propre)
// Classe parent class Animal { protected String nom; public Animal(String nom) { this.nom = nom; } public String manger() { return nom + " mange."; } } // Classe enfant class Chien extends Animal { // ← extends private String race; public Chien(String nom, String race) { super(nom); // Appelle le parent this.race = race; } public String aboyer() { return "Wouf wouf !"; } } Chien rex = new Chien("Rex", "Berger");
// Classe parent class Animal { protected string nom; public Animal(string nom) { this.nom = nom; } public virtual string Manger() { return $"{nom} mange."; } } // Classe enfant class Chien : Animal { // ← : = heritage private string race; public Chien(string nom, string race) : base(nom) { this.race = race; } public string Aboyer() { return "Wouf wouf !"; } } var rex = new Chien("Rex", "Berger");
// Classe parent class Animal { protected: std::string nom; public: Animal(std::string n) : nom(n) {} std::string manger() { return nom + " mange."; } }; // Classe enfant class Chien : public Animal { // ← : public = heritage private: std::string race; public: Chien(std::string n, std::string r) : Animal(n), race(r) {} std::string aboyer() { return "Wouf wouf !"; } }; Chien rex("Rex", "Berger");
super() - Appeler le parent
super().__init__(args)
super(args)
💡 super() permet d'appeler le constructeur ou les methodes de la classe parente.
Obligatoire si l'enfant a son propre constructeur et que le parent en a un aussi.
Surcharge de methode (Override)
L'enfant peut redefinir une methode du parent
class Animal: def parler(self): return "..." class Chien(Animal): def parler(self): # Override ! return "Wouf !" class Chat(Animal): def parler(self): # Override ! return "Miaou !" animaux = [Chien(), Chat(), Animal()] for a in animaux: print(a.parler()) # Wouf ! # Miaou ! # ...
class Animal { parler() { return "..."; } } class Chien extends Animal { parler() { // Override ! return "Wouf !"; } } class Chat extends Animal { parler() { // Override ! return "Miaou !"; } } const animaux = [new Chien(), new Chat(), new Animal()]; for (const a of animaux) { console.log(a.parler()); } // Wouf ! / Miaou ! / ...
class Animal { public String parler() { return "..."; } } class Chien extends Animal { @Override // Annotation recommandee public String parler() { return "Wouf !"; } } class Chat extends Animal { @Override public String parler() { return "Miaou !"; } } Animal[] animaux = {new Chien(), new Chat()}; for (Animal a : animaux) { System.out.println(a.parler()); }
Polymorphisme
"Plusieurs formes" - Un même appel produit des résultats différents selon l'objet
Le polymorphisme permet de traiter differents types d'objets de maniere uniforme. Chaque objet repond selon sa propre implementation.
Abstraction
L'abstraction consiste à cacher la complexité et ne montrer que l'essentiel. On définit un "contrat" que les classes enfants doivent respecter.
🚗 Quand tu conduis, tu utilises le volant et les pédales (interface simple). Tu n'as pas besoin de comprendre le moteur (complexité cachée) !
📝 Classe abstraite = Modèle incomplet
Une classe abstraite est un modèle qu'on ne peut pas instancier directement. Elle peut contenir des méthodes abstraites (sans code) que les enfants doivent implémenter.
from abc import ABC, abstractmethod class Forme(ABC): # Classe abstraite @abstractmethod def aire(self): pass # Pas de code ! Les enfants décident @abstractmethod def perimetre(self): pass class Rectangle(Forme): def __init__(self, largeur, hauteur): self.largeur = largeur self.hauteur = hauteur def aire(self): # Obligé d'implémenter ! return self.largeur * self.hauteur def perimetre(self): return 2 * (self.largeur + self.hauteur) # forme = Forme() ❌ Erreur ! Classe abstraite rect = Rectangle(5, 3) # ✅ OK
abstract class Forme { // Classe abstraite // Méthodes abstraites = contrat abstract double aire(); abstract double perimetre(); } class Rectangle extends Forme { private double largeur, hauteur; public Rectangle(double l, double h) { largeur = l; hauteur = h; } @Override // Obligé d'implémenter ! double aire() { return largeur * hauteur; } @Override double perimetre() { return 2 * (largeur + hauteur); } } // Forme f = new Forme(); ❌ Erreur ! Rectangle rect = new Rectangle(5, 3); // ✅ OK
- • Peut avoir du code + méthodes abstraites
- • Héritage simple (1 seul parent)
- • Partage du code commun
- • Que des signatures (pas de code)
- • Héritage multiple possible
- • Définit un "contrat"
Exemple pratique : Systeme de personnages
class Personnage: def __init__(self, nom, pv): self.nom = nom self.pv = pv def attaquer(self): return 10 # Degats de base class Guerrier(Personnage): def __init__(self, nom): super().__init__(nom, 150) # Plus de PV def attaquer(self): return 25 # Override: plus de degats class Mage(Personnage): def __init__(self, nom): super().__init__(nom, 80) self.mana = 100 def attaquer(self): if self.mana >= 10: self.mana -= 10 return 40 # Attaque magique puissante return 5 # Attaque faible sans mana # Polymorphisme en action ! equipe = [Guerrier("Conan"), Mage("Gandalf")] for perso in equipe: print(f"{perso.nom} inflige {perso.attaquer()} degats")
class Personnage { constructor(nom, pv) { this.nom = nom; this.pv = pv; } attaquer() { return 10; // Degats de base } } class Guerrier extends Personnage { constructor(nom) { super(nom, 150); // Plus de PV } attaquer() { return 25; // Override: plus de degats } } class Mage extends Personnage { constructor(nom) { super(nom, 80); this.mana = 100; } attaquer() { if (this.mana >= 10) { this.mana -= 10; return 40; // Magie ! } return 5; } } // Polymorphisme const equipe = [new Guerrier("Conan"), new Mage("Gandalf")]; for (const perso of equipe) { console.log(`${perso.nom} inflige ${perso.attaquer()} degats`); }
Avantages de l'heritage
Pas besoin de reecrire les methodes communes
Hierarchie claire : Animal → Chien → Berger
Traiter differents objets de facon uniforme
Ajouter de nouvelles classes facilement
📋 Recapitulatif
Felicitations !
Tu as termine le cours de Programmation !
Tu connais maintenant les fondamentaux pour coder dans n'importe quel langage.
Quiz Mode Survie
1 erreur = Game OverLes 4 Piliers de la POO
10 questions aleatoires - Mode Survie