Skip to content

POO : Préparation à l'examen ​

Dans ce TP, nous allons balayer l'ensemble des notions vues en POO à travers un cas complet : la gestion d'une médiathèque. C'est le type de sujet que vous pouvez rencontrer à l'examen : un diagramme UML à analyser, du code à corriger, du code à compléter et du code à écrire entièrement.

Sommaire

Ressources disponibles : ​

Objectifs ​

À la fin de ce TP vous saurez :

  • Lire et analyser un diagramme de classes (même présenté au format texte).
  • Repérer et corriger des erreurs classiques dans du code objet.
  • Compléter une classe à partir d'une modélisation.
  • Implémenter une interface et une classe abstraite.
  • Manipuler une collection d'objets grâce au polymorphisme.

La modélisation ​

Le jour de l'examen, le diagramme UML peut être fourni sous forme d'image… ou décrit textuellement. Voici la modélisation de notre médiathèque au format texte :

text
+--------------------------------------+
|            « abstract »              |
|              Document                |
+--------------------------------------+
| # titre : string                     |
| # annee : int                        |
| - disponible : bool = true           |
+--------------------------------------+
| + __construct(titre, annee)          |
| + getTitre() : string                |
| + estDisponible() : bool             |
| + abstract resume() : string         |
+--------------------------------------+
            ^                ^
            |                |
+-------------------+  +-------------------+
|       Livre       |  |        Dvd        |
+-------------------+  +-------------------+
| - auteur : string |  | - duree : int     |
| - nbPages : int   |  +-------------------+
+-------------------+  | + resume():string |
| + resume():string |  +-------------------+
+-------------------+

+--------------------------------------+
|            « interface »             |
|             Empruntable              |
+--------------------------------------+
| + emprunter(Adherent $a) : bool      |
| + rendre() : void                    |
+--------------------------------------+

Livre et Dvd implémentent l'interface Empruntable.

+--------------------------------------+
|              Adherent                |
+--------------------------------------+
| - nom : string                       |
| - prenom : string                    |
+--------------------------------------+
| + __construct(nom, prenom)           |
| + identite() : string                |
+--------------------------------------+

+--------------------------------------+
|             Mediatheque              |
+--------------------------------------+
| - nom : string                       |
| - lesDocuments : Document[]          |
+--------------------------------------+
| + __construct(nom)                   |
| + ajouter(Document $d) : void        |
| + afficherCatalogue() : void         |
| + nbDisponibles() : int              |
+--------------------------------------+

Mediatheque "1" ---- "0..*" Document (lesDocuments)

Rappel des symboles : + public, - privé (private), # protégé (protected).

Exercice 1 : Analyser l'UML ​

Avant d'écrire la moindre ligne de code, prenez le temps de lire la modélisation. C'est à vous de jouer !

Questionnement

  1. Combien de classes sont instanciables ? Lesquelles ?
  2. Pourquoi la classe Document est-elle abstraite ? Qu'est-ce que cela implique ?
  3. Quelle est la différence entre Empruntable et Document ? Pourquoi ne pas avoir mis emprunter() dans Document ?
  4. Que signifie la multiplicité "1" ---- "0..*" entre Mediatheque et Document ?
  5. La propriété titre est notée # (protected) alors que disponible est notée - (private). Quelle différence concrète cela fait-il pour les classes Livre et Dvd ?
  6. Si nous devions ajouter un document Magazine, quelles classes seraient impactées ?
Voir l'une des solutions possibles
  1. Quatre classes sont instanciables : Livre, Dvd, Adherent et Mediatheque. Document est abstraite (pas de new) et Empruntable est une interface (pas de new non plus).
  2. Document regroupe la logique commune (titre, année, disponibilité) mais un « document » générique n'a pas de sens seul : on ne sait pas rédiger son resume(). La rendre abstraite force les classes filles à implémenter resume() et interdit le new Document(…).
  3. Document factorise du code (héritage, « est un »), Empruntable définit un contrat (comportement, « est capable de »). Un futur objet non-document (une salle de réunion par exemple) pourrait être Empruntable sans être un Document.
  4. Une médiathèque possède de zéro à plusieurs documents ; un document appartient à une seule médiathèque.
  5. titre (protected) est accessible directement dans Livre et Dvd ($this->titre). disponible (private) ne l'est pas : les classes filles doivent passer par les méthodes publiques comme estDisponible().
  6. Une seule classe est à créer : Magazine extends Document (avec son resume()). Les autres classes ne changent pas, c'est tout l'intérêt de la modélisation : Mediatheque manipule des Document sans connaître leur type précis.

Exercice 2 : Corriger le code ​

Un étudiant a commencé l'implémentation en PHP, mais son code contient 7 erreurs (syntaxe, visibilité, respect de l'UML). À vous de les retrouver et de les corriger. Pas de panique, relisez l'UML ligne par ligne.

php
abstract class Document {
    protected titre;
    protected $annee;
    public $disponible = true;

    function __construct($titre, $annee){
        $this->titre = $titre;
        $this->annee = $annee;
    }

    public function getTitre(){
        return $titre;
    }

    public function estDisponible(){
        return $this->disponible;
    }

    public function resume(){
    }
}

class Livre extends Document {
    private $auteur;
    private $nbPages;

    function __construct($titre, $annee, $auteur, $nbPages){
        $this->auteur = $auteur;
        $this->nbPages = $nbPages;
    }

    public function resume($details){
        return "{$this->titre} ({$this->annee}), {$this->auteur}, {$this->nbPages} pages";
    }
}

$unDocument = new Document("Titre", 2020);

Point de contrôle

  • Vous avez trouvé les 7 erreurs ?
  • Pour chacune, savez-vous dire s'il s'agit d'une erreur de syntaxe, d'une erreur d'exécution ou d'un non-respect de l'UML ?
Voir l'une des solutions possibles
  1. protected titre; : il manque le $ (protected $titre;).
  2. public $disponible : l'UML indique -, la propriété doit être private.
  3. return $titre; dans getTitre() : il faut écrire return $this->titre;.
  4. resume() est déclarée avec du code (vide) : l'UML la définit abstraite, il faut écrire abstract public function resume(); (et supprimer le corps).
  5. Le constructeur de Livre n'appelle pas celui du parent : il faut ajouter parent::__construct($titre, $annee); (sinon titre et annee ne sont jamais initialisés).
  6. resume($details) dans Livre : la signature ne respecte pas celle de la classe mère (redéfinition = signature identique), il faut resume() sans paramètre.
  7. new Document(…) : impossible d'instancier une classe abstraite, cette ligne provoque une erreur fatale (Cannot instantiate abstract class Document).

Code corrigé :

php
abstract class Document {
    protected $titre;
    protected $annee;
    private $disponible = true;

    function __construct($titre, $annee){
        $this->titre = $titre;
        $this->annee = $annee;
    }

    public function getTitre(){
        return $this->titre;
    }

    public function estDisponible(){
        return $this->disponible;
    }

    abstract public function resume();
}

class Livre extends Document {
    private $auteur;
    private $nbPages;

    function __construct($titre, $annee, $auteur, $nbPages){
        parent::__construct($titre, $annee);
        $this->auteur = $auteur;
        $this->nbPages = $nbPages;
    }

    public function resume(){
        return "{$this->titre} ({$this->annee}), {$this->auteur}, {$this->nbPages} pages";
    }
}

Exercice 3 : Compléter la classe Dvd ​

Voici le squelette de la classe Dvd. Je vous laisse le compléter en respectant l'UML (les zones à compléter sont indiquées par des commentaires // À compléter) :

php
class Dvd extends Document {
    // À compléter : la ou les propriétés

    function __construct($titre, $annee, $duree){
        // À compléter : initialisation
    }

    public function resume(){
        // À compléter : retourne « <titre> (<annee>), durée <duree> min »
    }
}

Point de contrôle

  • Votre constructeur appelle-t-il celui du parent ?
  • duree est-elle bien private ?
  • resume() a-t-elle exactement la même signature que dans Document ?
Voir l'une des solutions possibles
php
class Dvd extends Document {
    private $duree;

    function __construct($titre, $annee, $duree){
        parent::__construct($titre, $annee);
        $this->duree = $duree;
    }

    public function resume(){
        return "{$this->titre} ({$this->annee}), durée {$this->duree} min";
    }
}

Exercice 4 : Implémenter l'interface Empruntable ​

Il est maintenant temps d'écrire le contrat et de le faire respecter. C'est à vous de jouer !

  1. Écrire l'interface Empruntable en PHP (voir l'UML).
  2. Écrire la classe Adherent (constructeur + identite() qui retourne « Prenom NOM »).
  3. Modifier Livre et Dvd pour qu'ils implémentent Empruntable :
    • emprunter(Adherent $a) retourne false si le document n'est pas disponible, sinon il passe le document en indisponible et retourne true.
    • rendre() repasse le document en disponible.

Un instant !

La propriété disponible est private dans Document… Vos méthodes emprunter() et rendre() ne peuvent donc pas y accéder directement depuis Livre ou Dvd. Deux pistes : ajouter un mutateur protected dans Document, ou implémenter emprunter() / rendre() directement dans Document. Laquelle choisissez-vous, et pourquoi ?

Voir l'une des solutions possibles
php
interface Empruntable {
    public function emprunter(Adherent $a): bool;
    public function rendre(): void;
}

class Adherent {
    private $nom;
    private $prenom;

    function __construct($nom, $prenom){
        $this->nom = $nom;
        $this->prenom = $prenom;
    }

    public function identite(){
        return $this->prenom . " " . strtoupper($this->nom);
    }
}

abstract class Document implements Empruntable {
    protected $titre;
    protected $annee;
    private $disponible = true;

    function __construct($titre, $annee){
        $this->titre = $titre;
        $this->annee = $annee;
    }

    public function getTitre(){
        return $this->titre;
    }

    public function estDisponible(){
        return $this->disponible;
    }

    public function emprunter(Adherent $a): bool {
        if(!$this->disponible){
            return false;
        }

        $this->disponible = false;
        return true;
    }

    public function rendre(): void {
        $this->disponible = true;
    }

    abstract public function resume();
}

Ici, la solution retenue est d'implémenter emprunter() et rendre() une seule fois dans Document : le code est factorisé et disponible reste private. Livre et Dvd n'ont rien à ajouter, ils héritent du comportement.

Exercice 5 : La médiathèque et le polymorphisme ​

Dernière étape, assembler le tout. Écrire la classe Mediatheque puis le programme principal :

  1. ajouter(Document $d) ajoute un document dans la collection lesDocuments.
  2. afficherCatalogue() affiche le resume() de chaque document (une ligne par document).
  3. nbDisponibles() retourne le nombre de documents disponibles.
  4. Programme principal :
    • Créer une médiathèque, y ajouter 2 livres et 2 DVD.
    • Créer un adhérent et lui faire emprunter un document.
    • Afficher le catalogue puis le nombre de documents disponibles.

Questionnement

  • Dans afficherCatalogue(), vous appelez resume() sans connaître le type réel de l'objet (Livre ou Dvd). Comment s'appelle ce mécanisme ?
  • Pourquoi la collection est-elle typée Document[] et pas Livre[] ?
  • Que se passerait-il si une classe fille de Document « oubliait » d'écrire resume() ?
Voir l'une des solutions possibles
php
class Mediatheque {
    private $nom;
    private $lesDocuments = [];

    function __construct($nom){
        $this->nom = $nom;
    }

    public function ajouter(Document $d): void {
        $this->lesDocuments[] = $d;
    }

    public function afficherCatalogue(): void {
        foreach ($this->lesDocuments as $leDocument) {
            // Polymorphisme : le resume() appelé est celui du type réel (Livre ou Dvd)
            echo $leDocument->resume() . "\n";
        }
    }

    public function nbDisponibles(): int {
        $compteur = 0;
        foreach ($this->lesDocuments as $leDocument) {
            if($leDocument->estDisponible()){
                $compteur++;
            }
        }
        return $compteur;
    }
}

// Programme principal
$mediatheque = new Mediatheque("Médiathèque du lycée");

$mediatheque->ajouter(new Livre("1984", 1949, "George Orwell", 328));
$mediatheque->ajouter(new Livre("Dune", 1965, "Frank Herbert", 412));

$dvd = new Dvd("Interstellar", 2014, 169);
$mediatheque->ajouter($dvd);
$mediatheque->ajouter(new Dvd("Le Voyage de Chihiro", 2001, 125));

$adherent = new Adherent("Brosseau", "Valentin");
$dvd->emprunter($adherent);

$mediatheque->afficherCatalogue();
echo "Documents disponibles : " . $mediatheque->nbDisponibles() . "\n"; // 3

Réponses aux questions :

  • C'est le polymorphisme : la méthode exécutée est choisie dynamiquement selon le type réel de l'objet.
  • Typer la collection Document[] permet d'y ranger tous les types de documents (actuels et futurs). Avec Livre[], impossible d'y mettre un Dvd.
  • La classe fille serait obligée d'être déclarée abstract à son tour, sinon PHP lève une erreur : une classe concrète doit implémenter toutes les méthodes abstraites héritées.

Conclusion ​

Vous venez de dérouler un sujet type examen :

  • Analyse d'un diagramme de classes (visibilités, multiplicités, abstraction, interface).
  • Correction d'un code qui ne respecte pas l'UML.
  • Complétion et implémentation de classes (héritage, interface, contrat).
  • Manipulation d'une collection grâce au polymorphisme.

Si l'un des cinq exercices vous a résisté, retournez voir la mise en pratique correspondante (classes abstraites, interfaces, redéfinition, polymorphisme).

👋 Si vous avez des questions, n'hésitez pas.

×

Reformulation

La reformulation (IA exécutée localement dans votre navigateur) peut faire des erreurs. Envisagez de vérifier les informations.