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.

Animal + nom + manger() nom · manger() nom · manger() Chien nom (hérité) manger() (hérité) + aboyer() Chat nom (hérité) manger() (hérité) + miauler()

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.

📦
Encapsulation
Cacher les détails internes
🧬
Héritage
Réutiliser du code parent
🎭
Polymorphisme
Même interface, comportements différents
🔮
Abstraction
Simplifier la complexité
Concepts clés abordés
🧬 Héritage (extends) 🎭 Polymorphisme 🔒 Visibilité (public/private) 📝 Classes abstraites 📋 Interfaces 🔄 Override / super

🎯 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 !

📦
Pilier 1

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
Accessible partout
🌍
protected
Classe + enfants
👨‍👧
private
Classe uniquement
🔐
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 !

🧬
Pilier 2

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

🐾 Animal
nom, age, manger()
🐕 Chien
race, aboyer()
🐈 Chat
miauler()
🐦 Oiseau
voler()

💡 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

Python
super().__init__(args)
JS / Java
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());
}
🎭
Pilier 3

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.

Clique pour entendre l'animal parler :
🐕 Chien
🐈 Chat
🐄 Vache
🐓 Coq
Resultat : Clique sur un animal
🔮
Pilier 4

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
📝 Classe Abstraite
  • • Peut avoir du code + méthodes abstraites
  • • Héritage simple (1 seul parent)
  • • Partage du code commun
📋 Interface (→ Fiche 9)
  • • 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

🔄 Reutilisation du code

Pas besoin de reecrire les methodes communes

📦 Organisation logique

Hierarchie claire : Animal → Chien → Berger

🎭 Polymorphisme

Traiter differents objets de facon uniforme

🛠️ Extensibilite

Ajouter de nouvelles classes facilement

📋 Recapitulatif

Heritage
extends / class Enfant(Parent)
super()
Appeler le parent
Override
Redefinir une methode
Polymorphisme
Meme interface, comportements differents
🎉

Felicitations !

Tu as termine le cours de Programmation !
Tu connais maintenant les fondamentaux pour coder dans n'importe quel langage.

Variables → Conditions → Boucles → Fonctions → Tableaux → Classes → Heritage ✓
🎯

Quiz Mode Survie

1 erreur = Game Over
🧬

Les 4 Piliers de la POO

10 questions aleatoires - Mode Survie

Prog 7 Retour au Hub 🏠
🎬

Pour aller plus loin

Les excellentes vidéos de Graven sur l'héritage :

Java Héritage
Apprendre le Java #8 - L'Héritage
Graven • ☕ Java
Python Héritage
Apprendre le Python #8 - L'Héritage
Graven • 🐍 Python
Glisser pour continuer vers Prog 9 : Interfaces
⬇️
Avancé