Programmation Java

Classe SDA 7 2025-2026

Module 5

La Programmation Orientée Objet (POO)

Les Quatre Piliers : Encapsulation, Héritage, Polymorphisme, Abstraction

Introduction : Les Quatre Piliers de la POO

La Programmation Orientée Objet repose sur quatre concepts fondamentaux qui transforment votre façon de concevoir les programmes. Ces piliers permettent de créer du code plus organisé, réutilisable et maintenable.

🏛️ Les Quatre Piliers
  • Encapsulation - Protéger les données
  • Héritage - Réutiliser le code
  • Polymorphisme - Flexibilité et extensibilité
  • Abstraction - Simplifier la complexité

1. Révision : Classes, Objets et Constructeurs

Une classe est un plan, un objet est une instance concrète, et le constructeur est la "chaîne de montage" qui assemble l'objet en initialisant ses attributs.

La surcharge de constructeurs est une technique puissante qui offre plus de flexibilité lors de la création d'objets.

Exemple 1 : Surcharge de Constructeurs

Cet exemple montre comment créer des objets Student de différentes manières :

class Student { String name; int age; String major; // Constructeur n°1 : complet public Student(String name, int age, String major) { this.name = name; this.age = age; this.major = major; } // Constructeur n°2 : surchargé, avec des valeurs par défaut public Student(String name) { this(name, 18, "Undeclared"); } public void displayInfo() { System.out.println("Nom: " + name + ", Âge: " + age + ", Spécialité: " + major); } } public class Main { public static void main(String[] args) { Student student1 = new Student("John", 20, "Informatique"); student1.displayInfo(); Student student2 = new Student("Jane"); student2.displayInfo(); } }
Astuce : this() pour Éviter la Duplication

L'instruction this(name, 18, "Undeclared"); appelle un autre constructeur de la même classe pour éviter de dupliquer le code d'initialisation. C'est une excellente pratique !

Pilier n°1 : L'Encapsulation

Définition

C'est l'idée de regrouper les données (attributs) et les méthodes qui les manipulent au sein d'un même "objet", tout en cachant les détails internes au monde extérieur.

Objectif Principal

Protéger l'intégrité des données d'un objet. Un objet doit être le seul maître de son état interne pour éviter qu'il ne soit corrompu par des modifications invalides ou accidentelles.

Analogie : Le Tableau de Bord d'une Voiture

En tant que conducteur, vous interagissez avec une interface simple : un volant, des pédales, un levier de vitesse. Vous n'avez pas besoin de manipuler directement les injecteurs de carburant ou la transmission. La complexité du moteur est cachée (private). Vous utilisez des méthodes publiques (public) comme accelerer() ou freiner(), et la voiture gère elle-même sa mécanique interne de manière sûre.

Implémentation en Java

  • On déclare les attributs avec le mot-clé private
  • On fournit un accès contrôlé via des getters (pour lire) et setters (pour modifier)

Exemple 2 : Encapsulation avec BankAccount

Cet exemple montre comment protéger le solde d'un compte :

class BankAccount { private String accountNumber; private double balance; // Privé ! Personne ne peut le modifier directement. public BankAccount(String accountNumber, double initialBalance) { this.accountNumber = accountNumber; this.balance = initialBalance; } // Getter : seule façon de LIRE le solde public double getBalance() { return this.balance; } // Méthode publique pour déposer de l'argent public void deposit(double amount) { if (amount > 0) { this.balance += amount; System.out.println(amount + " € déposés. Nouveau solde : " + this.balance + " €."); } else { System.out.println("Le montant du dépôt doit être positif."); } } // Méthode publique pour retirer de l'argent public void withdraw(double amount) { if (amount > 0 && amount <= this.balance) { this.balance -= amount; System.out.println(amount + " € retirés. Nouveau solde : " + this.balance + " €."); } else { System.out.println("Fonds insuffisants ou montant invalide."); } } } public class Main { public static void main(String[] args) { BankAccount account = new BankAccount("123456789", 1000); System.out.println("Solde initial : " + account.getBalance() + " €."); account.deposit(500); account.withdraw(2000); // Échoue car fonds insuffisants account.withdraw(300); } }

Résultat :

Solde initial : 1000.0 €. 500.0 € déposés. Nouveau solde : 1500.0 €. Fonds insuffisants ou montant invalide. 300.0 € retirés. Nouveau solde : 1200.0 €.
Bénéfices de l'Encapsulation
  • Sécurité - Les données sont protégées
  • Contrôle - Vous pouvez ajouter de la logique de validation
  • Flexibilité - Vous pouvez changer l'implémentation interne sans affecter l'interface publique

Pour aller plus loin :

Source: EvoluNoob

Pilier n°2 : L'Héritage

Définition

C'est un mécanisme qui permet à une nouvelle classe (classe "fille") de recevoir les attributs et les méthodes d'une classe existante (classe "mère").

Objectif Principal

Réutiliser le code et créer une hiérarchie logique entre les classes. L'héritage modélise une relation de type "est un". Par exemple, un Chien est un Animal.

Analogie : Les Véhicules

Tous les véhicules ont des caractéristiques communes (une vitesse, une capacité de passagers). On peut créer une classe Vehicule avec ces éléments. Ensuite, une classe Voiture peut hériter de Vehicule et ajouter des attributs spécifiques comme nombreDePortes.

Implémentation en Java

  • On utilise le mot-clé extends pour définir la relation d'héritage
  • On utilise le mot-clé super pour appeler le constructeur ou les méthodes de la classe mère

Exemple 3 : Héritage avec Animal

// Classe Mère (générale) class Animal { String name; public Animal(String name) { this.name = name; } public void eat() { System.out.println(name + " mange."); } } // Classe Fille (spécifique), hérite de Animal class Dog extends Animal { public Dog(String name) { super(name); // Appelle le constructeur de la classe mère } // Méthode spécifique à Dog public void bark() { System.out.println(name + " aboie : Woof !"); } } public class Main { public static void main(String[] args) { Dog myDog = new Dog("Rex"); myDog.eat(); // Méthode héritée de Animal myDog.bark(); // Méthode propre à Dog } }
Rôle de super()

super(name) est obligatoire ici pour appeler le constructeur de Animal et initialiser l'attribut name qui est défini dans la classe mère.

Pour aller plus loin :

Source: EvoluNoob

Pilier n°3 : Le Polymorphisme

Définition

"Qui peut prendre plusieurs formes". C'est la capacité d'un objet d'être traité comme une instance de sa propre classe, mais aussi comme une instance de sa classe mère. Une seule action peut avoir des comportements différents selon le type réel de l'objet.

Objectif Principal

Écrire du code générique et flexible qui peut fonctionner avec une multitude d'objets différents sans avoir à connaître leur type exact.

Analogie : La Prise Électrique

Vous avez une prise murale (l'interface). Vous pouvez y brancher une lampe, un chargeur de téléphone, ou un aspirateur. L'action est toujours la même : "fournir du courant". Cependant, le résultat est très différent pour chaque appareil. Le polymorphisme vous permet d'écrire un code qui dit "fournir du courant" sans se soucier de l'appareil qui est branché.

Implémentation en Java

Principalement via la redéfinition de méthode (@Override) dans une hiérarchie d'héritage. Une variable de type Animal peut contenir un objet Chien ou Chat, et l'appel à la méthode makeSound() exécutera la version spécifique à l'objet réel.

Exemple 4 : Polymorphisme en Action

class Animal { public void makeSound() { System.out.println("L'animal fait un bruit."); } } class Dog extends Animal { @Override // Annotation qui indique qu'on redéfinit une méthode mère public void makeSound() { System.out.println("Le chien aboie."); } } class Cat extends Animal { @Override public void makeSound() { System.out.println("Le chat miaule."); } } public class Main { public static void main(String[] args) { // Le polymorphisme permet cette affectation Animal myAnimal1 = new Dog(); Animal myAnimal2 = new Cat(); // La JVM sait quelle version appeler au moment de l'exécution myAnimal1.makeSound(); // Affiche: Le chien aboie. myAnimal2.makeSound(); // Affiche: Le chat miaule. } }
L'Annotation @Override

Cette annotation n'est pas obligatoire mais c'est une excellente pratique. Elle demande au compilateur de vérifier que vous êtes bien en train de redéfinir une méthode qui existe dans la classe mère.

Pour aller plus loin :

Source: EvoluNoob

Pilier n°4 : L'Abstraction

Définition

Cacher les détails d'implémentation complexes et n'exposer que les fonctionnalités essentielles et de haut niveau d'un objet. L'abstraction se concentre sur CE QUE un objet fait, et non sur COMMENT il le fait.

Objectif Principal

Réduire la complexité et permettre de travailler sur des systèmes à grande échelle sans avoir à en comprendre chaque détail.

Analogie : La Télécommande de la Télévision

Votre télécommande est une interface abstraite. Elle a des boutons simples : "Allumer", "Volume +", "Chaîne -". Vous savez ce que ces boutons font. Mais vous n'avez aucune idée de la complexité des circuits électroniques et des logiciels internes qui s'exécutent. Cette complexité est abstraite, cachée.

Implémentation en Java

  • Classes Abstraites (abstract class) : Un mélange entre un plan et une implémentation partielle
  • Interfaces (interface) : Un contrat 100% abstrait

Exemple 5 : Polymorphisme avec une Interface

// L'interface définit un contrat interface Shape { double calculateArea(); // Pas de corps, juste la signature } // Cette classe respecte le contrat class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double calculateArea() { return Math.PI * radius * radius; } } // Cette classe respecte aussi le contrat class Rectangle implements Shape { private double width, height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double calculateArea() { return width * height; } } public class Main { public static void main(String[] args) { Shape myShape1 = new Circle(5); Shape myShape2 = new Rectangle(4, 7); System.out.println("Aire de la forme 1 : " + myShape1.calculateArea()); System.out.println("Aire de la forme 2 : " + myShape2.calculateArea()); } }
Quand Utiliser Quoi ?
  • Classe Abstraite : Quand vous avez du code commun à partager
  • Interface : Quand vous voulez juste définir un contrat

Résumé des Quatre Piliers

Pilier Objectif Bénéfice
Encapsulation Protéger les données Sécurité et contrôle
Héritage Réutiliser le code DRY et organisation
Polymorphisme Flexibilité du code Extensibilité
Abstraction Simplifier la complexité Maintenabilité

Exercices Pratiques : Mettre les Piliers en Action

Ces exercices vous permettront de mettre en pratique chaque pilier de la POO pour construire des classes bien structurées et robustes.

Exercice 1 : L'Encapsulation (Classe `Produit`)

Créez une classe `Produit` pour représenter un article dans un magasin. Elle doit être correctement encapsulée :

  • Attributs privés : `nom` (String) et `prix` (double).
  • Un constructeur pour initialiser le nom et le prix.
  • Un getter pour le `nom` et le `prix`.
  • Un setter pour le `prix` qui empêche de définir un prix négatif. Si le prix est négatif, affichez un message d'erreur et ne changez pas le prix.

Testez votre classe en créant un produit, en affichant ses informations, puis en essayant de mettre à jour son prix avec une valeur valide et une valeur invalide.

Indice : Votre setter `setPrix(double nouveauPrix)` doit contenir une condition `if (nouveauPrix >= 0)`.

Voir la solution
// Fichier: Produit.java public class Produit { private String nom; private double prix; public Produit(String nom, double prix) { this.nom = nom; // On utilise le setter même dans le constructeur pour appliquer la validation this.setPrix(prix); } public String getNom() { return this.nom; } public double getPrix() { return this.prix; } public void setPrix(double nouveauPrix) { if (nouveauPrix >= 0) { this.prix = nouveauPrix; } else { System.out.println("Erreur : Le prix ne peut pas être négatif."); } } } // Fichier: Magasin.java public class Magasin { public static void main(String[] args) { Produit telephone = new Produit("Smartphone X", 599.99); System.out.println("Produit : " + telephone.getNom() + ", Prix : " + telephone.getPrix() + "€"); System.out.println("\nMise à jour du prix à -50€..."); telephone.setPrix(-50.0); // Devrait afficher une erreur System.out.println("Prix actuel : " + telephone.getPrix() + "€"); System.out.println("\nMise à jour du prix à 649.99€..."); telephone.setPrix(649.99); System.out.println("Prix actuel : " + telephone.getPrix() + "€"); } }

Exercice 2 : L'Héritage (Classes `Employe` et `Manager`)

Créez une classe de base `Employe` avec les attributs `nom` et `salaire`. Créez ensuite une classe `Manager` qui hérite de `Employe`. La classe `Manager` doit avoir un attribut supplémentaire : `departement` (String).

  • Le constructeur de `Manager` doit accepter un nom, un salaire et un département, et appeler le constructeur de la classe `Employe` pour initialiser le nom et le salaire.
  • Ajoutez une méthode `afficherDetails()` dans les deux classes pour afficher les informations spécifiques à chacune.

Indice : Utilisez `super(nom, salaire);` dans le constructeur de `Manager` et l'annotation `@Override` pour la méthode `afficherDetails()` de `Manager`.

Voir la solution
// Fichier: Employe.java public class Employe { protected String nom; protected double salaire; public Employe(String nom, double salaire) { this.nom = nom; this.salaire = salaire; } public void afficherDetails() { System.out.println("Nom : " + nom + ", Salaire : " + salaire + "€"); } } // Fichier: Manager.java public class Manager extends Employe { private String departement; public Manager(String nom, double salaire, String departement) { // Appel au constructeur de la classe mère (Employe) super(nom, salaire); this.departement = departement; } @Override public void afficherDetails() { // Appel à la méthode de la classe mère pour ne pas dupliquer le code super.afficherDetails(); System.out.println("Département : " + this.departement); } } // Fichier: Entreprise.java public class Entreprise { public static void main(String[] args) { Employe emp = new Employe("Alice", 35000); Manager mgr = new Manager("Bob", 60000, "Technologie"); System.out.println("Détails de l'employé :"); emp.afficherDetails(); System.out.println("\nDétails du manager :"); mgr.afficherDetails(); } }

Exercice 3 : Polymorphisme et Abstraction (Interface `Dessinable`)

Mettez en pratique le polymorphisme et l'abstraction :

  • Créez une interface `Dessinable` avec une seule méthode abstraite : `void dessiner()`.
  • Créez deux classes, `Cercle` et `Rectangle`, qui implémentent l'interface `Dessinable`. Chaque classe doit fournir sa propre version de la méthode `dessiner()` (par exemple, en affichant "Je dessine un cercle." ou "Je dessine un rectangle.").
  • Dans votre `main`, créez un tableau de type `Dessinable[]` contenant un objet `Cercle` et un objet `Rectangle`.
  • Parcourez ce tableau avec une boucle et appelez la méthode `dessiner()` sur chaque élément.

Indice : Le polymorphisme vous permet de stocker des `Cercle` et des `Rectangle` dans un tableau de `Dessinable`. La JVM saura quelle méthode `dessiner()` appeler pour chaque objet.

Voir la solution
// Fichier: Dessinable.java (Interface) public interface Dessinable { void dessiner(); // Méthode abstraite par défaut } // Fichier: Cercle.java public class Cercle implements Dessinable { @Override public void dessiner() { System.out.println("Je dessine un cercle. (O)"); } } // Fichier: Rectangle.java public class Rectangle implements Dessinable { @Override public void dessiner() { System.out.println("Je dessine un rectangle. [ ]"); } } // Fichier: Main.java public class Main { public static void main(String[] args) { // Le tableau est de type interface, mais contient des objets de classes concrètes Dessinable[] formes = new Dessinable[2]; formes[0] = new Cercle(); formes[1] = new Rectangle(); // Grâce au polymorphisme, on peut parcourir le tableau de manière uniforme for (Dessinable forme : formes) { forme.dessiner(); // La JVM appelle la bonne méthode } } }