Le paradigme objet

Université de Toulon

LIS UMR CNRS 7020

2026-11-09

Feuille de route : le paradigme avant le langage

  • Cours de modélisation objet : les concepts d’abord. Les démos sont écrites en Java — lisez-y des messages, pas de la syntaxe.
  • Trajet : l’objet et ses messages → les classes → la spécialisation et le polymorphisme → le diagramme de classes → le même concept dans quatre autres langages → Java, en dernier volet.
  • En pratique : seules les cellules Java sont exécutées par le noyau du carnet ; les extraits des quatre autres langages du panorama (Python, Kotlin, TypeScript, JavaScript) s’y lisent sans s’exécuter, à titre de comparaison.

Aux origines de l’approche objet

  • Années 1960 — la simulation : il faut faire évoluer de front des modèles à multiples composantes ; l’objet naît de ce problème, pas d’un problème d’écriture de code.
  • Diffusion du paradigme : en programmation (Smalltalk, C++, Java, etc.), en modélisation (UML), en bases de données (SGBDOO, aujourd’hui presque disparus, et SGBDRO).
  • Concept majeur : l’encapsulation ; autour de lui, l’héritage — que nous aborderons ici même — ainsi que la composition et la délégation, que le cours développera aux séances de réutilisation.
  • Communication par messages : un objet adresse à un autre des messages prédéfinis, sans connaître son implémentation interne.
  • Regroupement en classes : les objets de même forme sont décrits une seule fois.
  • Deux points de vue sur un objet : structurel — instance d’un type masquant une structure derrière des opérations ; conceptuel — entité du monde réel, généralisable ou spécialisée.

L’objet : identité, état, comportement

Définition 1 (Objet) Représente une entité dotée d’une identité, d’un état (attributs) et d’un comportement (méthodes).

  • État interne (attributs) : noms et valeurs qui décrivent l’objet à un instant donné.
  • Comportement (méthodes) : réagir aux messages reçus selon cet état, et pouvoir le modifier.
  • Encapsulation : l’état est masqué ; on n’y accède que par les opérations que l’objet fournit.
  • Avantages : sûreté — les données internes sont protégées des écritures non autorisées ; évolutivité — on peut changer l’implémentation sans toucher au code qui utilise l’objet.
  • Coût : performance — les méthodes d’accès introduisent une légère surcharge.

Messages et contrat : utiliser sans lire

  • Un objet possède un ensemble de méthodes, publiques ou privées.
  • Interface publique = l’ensemble des méthodes publiques : les messages que l’objet accepte de recevoir, et les informations qu’il accepte de communiquer.
  • Écrire contre une interface, c’est n’avoir besoin que de cette liste — pas du code qui derrière.
  • Exemple de contrat pour un objet unePersonne : quatre messages — saluer(), donnerPrenom(p), afficherPrenom(), retournerPrenom() — exécutés plus bas.

Voici le type, puis les quatre messages — saluer(), donnerPrenom(p), afficherPrenom(), retournerPrenom() — exécutés dans la seconde cellule.

class Personne {
    String prenom = "";
    void saluer() { System.out.println("Bonjour, je suis " + prenom); }
    void donnerPrenom(String p) { prenom = p; }
    String retournerPrenom() { return prenom; }
    void afficherPrenom() { System.out.println(prenom); }
}
Personne unePersonne = new Personne();
unePersonne.donnerPrenom("Pierre");
unePersonne.saluer();
unePersonne.afficherPrenom();
unePersonne.retournerPrenom();
Bonjour, je suis Pierre
Pierre
unePersonne.retournerPrenom();: Pierre

Le contrat de cette carte tient en quatre messages publics — valider(), recharger(voyages), voyagesRestants(), voyagesConsommes() — et un constructeur qui reçoit un nombre de voyages initiaux. Nous n’avons pas lu son code, et nous n’allons pas le lire : les lignes ci-dessous envoient ces messages à un objet, rien de plus. Deux notations vous concernent peu ici — new et le typage de la boucle for ; vous les verrez en détail plus tard. Lisez seulement les messages envoyés et les réponses reçues. Le constructeur reçoit 10 voyages, puis une boucle tente 12 validations consécutives — les deux dernières itérations doivent donc buter sur le refus.

CarteDeTransport carte = new CarteDeTransport(10);
for (int i = 1; i <= 12; i++) {
    boolean ok = carte.valider();
    if (ok) {
        System.out.println("Voyage " + i + " : validé — restants=" + carte.voyagesRestants() + ", consommés=" + carte.voyagesConsommes());
    } else {
        System.out.println("Voyage " + i + " : REFUSÉ — restants=" + carte.voyagesRestants() + ", consommés=" + carte.voyagesConsommes());
    }
}
Voyage 1 : validé — restants=9, consommés=1
Voyage 2 : validé — restants=8, consommés=2
Voyage 3 : validé — restants=7, consommés=3
Voyage 4 : validé — restants=6, consommés=4
Voyage 5 : validé — restants=5, consommés=5
Voyage 6 : validé — restants=4, consommés=6
Voyage 7 : validé — restants=3, consommés=7
Voyage 8 : validé — restants=2, consommés=8
Voyage 9 : validé — restants=1, consommés=9
Voyage 10 : validé — restants=0, consommés=10
Voyage 11 : REFUSÉ — restants=0, consommés=10
Voyage 12 : REFUSÉ — restants=0, consommés=10
  • Ce que l’on regarde : deux compteurs que nous n’avons jamais écrits nous-mêmes, et un booléen.
  • Itérations 1 à 10 : validé, restants décroît de 10 à 0, consommés croît de 0 à 10.
  • Itérations 11 et 12 : REFUSÉ, et les deux compteurs se figent — aucune valeur négative.
  • Le refus n’est pas une exception : valider() répond false ; c’est l’appelant qui décide d’afficher.
  • recharger est sans garde : une recharge négative casserait l’invariant — cas d’étude annoncé.
  • Leçon : le code de la classe nous était inutile pour l’utiliser.

Classes : un patron, des exemplaires

  • Certains objets s’écrivent de la même main : deux cartes, deux abonnés, deux véhicules — mêmes informations, mêmes opérations.

  • Classe : ensemble d’objets présentant des similitudes d’identification, de comportement ou de description de l’état. Une instance est un objet de cette classe.

  • Une classe, un seul texte ; chaque new alloue un état propre.

    Voiture v1 = new Voiture("Toyota", "Corolla", 2020, "rouge");
    Voiture v2 = new Voiture("Honda", "Civic", 2018, "bleue");
  • Le constructeur initialise l’état de l’exemplaire créé ; c’est le point d’entrée de toute instance.

  • Données par instance : voyagesRestants et voyagesConsommes existent en deux copies, une par objet.

  • À distinguer des données de classe : un membre marqué static — en UML {static} nombreDeVoitures: int, notation présentée juste après — appartient à la classe et est partagé par toutes les instances.

Spécialiser et polymorphiser

  • Un patron peut en spécialiser un autre : la spécialisation reprend l’état et le comportement de sa généralité, et ajoute ou ajuste le sien.
  • Relation, pas syntaxe : généraliser et spécialiser existent en UML comme en Python, en Kotlin ou en Java — c’est une façon d’organiser un modèle.
  • Un message, plusieurs réponses : valider() envoyé à une carte de transport, à un abonnement de vélo ou à un pass annuel est le même message ; chacun y répond selon son propre état.
  • Polymorphisme : écrire le code contre la généralité — le contrat commun — et non contre un cas particulier.
  • Gain : ajouter une spécialisation ne modifie pas le code qui envoie le message.

UML : modéliser très brièvement

Le diagramme de classes est la notation par laquelle UML — langage de modélisation — décrit une classe. En voici un, tracé d’après le contrat que nous avons déjà utilisé sans le lire.

Figure 1: Diagramme de classes de CarteDeTransport

  • Trois zones : le nom de la classe, la liste de ses attributs, la liste de ses méthodes.
  • Un membre se lit nom: Type ; Java écrit Type nom — - voyagesRestants: int devient private int voyagesRestants;.
  • Visibilité en préfixe : - privé, invisible de l’extérieur ; + public, seul à constituer le contrat.
  • Le diagramme dit QUOI, pas COMMENT : ce que l’on peut demander, jamais comment valider() fonctionne.

Le même concept, quatre autres langages

Une seule classe, Voiture, écrite dans cinq langages. Les concepts sont identiques ; les choix de rendu ne le sont pas, et chacun est raisonnable dans son contexte. Voici d’abord la version Java, qui nous sert de référence — exécutée, avec ses quatre attributs privés, ses accesseurs, ses trois comportements et son compteur partagé.

public class Voiture {
    private String marque;
    private String modele;
    private int annee;
    private String couleur;
    private static int nombreDeVoitures = 0;

    public Voiture(String marque, String modele, int annee, String couleur) {
        this.marque = marque;
        this.modele = modele;
        this.annee = annee;
        this.couleur = couleur;
        nombreDeVoitures++;
    }

    public void demarrer() {
        System.out.println(marque + " " + modele + " démarre.");
    }

    public void arreter() {
        System.out.println(marque + " " + modele + " s'arrête.");
    }

    public void accelerer(int vitesse) {
        System.out.println(marque + " " + modele + " accélère à " + vitesse + " km/h.");
    }

    public String getMarque() {
        return marque;
    }

    public String getModele() {
        return modele;
    }

    public int getAnnee() {
        return annee;
    }

    public String getCouleur() {
        return couleur;
    }

    public static int getNombreDeVoitures() {
        return nombreDeVoitures;
    }
}

Grille de lecture. Chaque snippet ci-dessous montre le MÊME modèle Voiture — un extrait comparatif : dans ce carnet, seules les cellules Java sont exécutées par le noyau ; ceux des quatre autres langages se lisent sans s’exécuter. N’y lisez que deux choses : comment les attributs sont déclarés et comment le constructeur s’écrit — le reste est identique. La notation de la spécialisation, elle, figure dans le tableau comparatif en fin de section.

Python

class Voiture:
    nombre_de_voitures = 0

    def __init__(self, marque, modele, annee, couleur):
        self.marque = marque
        self.modele = modele
        self.annee = annee
        self.couleur = couleur
        Voiture.nombre_de_voitures += 1

    def demarrer(self):
        print(f"{self.marque} {self.modele} démarre.")

    def arreter(self):
        print(f"{self.marque} {self.modele} s'arrête.")

    def accelerer(self, vitesse):
        print(f"{self.marque} {self.modele} accélère à {vitesse} km/h.")

    @classmethod
    def get_nombre_de_voitures(cls):
        return cls.nombre_de_voitures

v = Voiture("Renault", "Clio", 2021, "bleue")
v.demarrer()
v.accelerer(90)
print(v.__dict__)
print(Voiture.get_nombre_de_voitures())

Chaque extrait ne construit que les objets qu’il déclare lui-même : le compteur qu’on y lit repart de zéro d’un langage à l’autre.

Kotlin

// le reste est identique — seule la notation change
class Voiture(
    private val marque: String,
    private val modele: String,
    private val annee: Int,
    private val couleur: String
) {
    companion object {
        var nombreDeVoitures: Int = 0
            private set
    }

    init {
        nombreDeVoitures++
    }

    fun demarrer() {
        println("$marque $modele démarre.")
    }

    fun arreter() {
        println("$marque $modele s'arrête.")
    }

    fun accelerer(vitesse: Int) {
        println("$marque $modele accélère à $vitesse km/h.")
    }
}

TypeScript

// le reste est identique — seule la notation change
class Voiture {
    private static nombreDeVoitures: number = 0;

    constructor(
        private marque: string,
        private modele: string,
        private annee: number,
        private couleur: string
    ) {
        Voiture.nombreDeVoitures++;
    }

    public demarrer(): void {
        console.log(`${this.marque} ${this.modele} démarre.`);
    }

    public arreter(): void {
        console.log(`${this.marque} ${this.modele} s'arrête.`);
    }

    public accelerer(vitesse: number): void {
        console.log(`${this.marque} ${this.modele} accélère à ${vitesse} km/h.`);
    }

    public static getNombreDeVoitures(): number {
        return Voiture.nombreDeVoitures;
    }
}

JavaScript — rendu dans sa forme prototype, celle sur laquelle tout repose : la syntaxe class d’ES6 est du sucre sur les prototypes.

// le reste est identique — seule la notation change
// Constructeur Voiture
function Voiture(marque, modele, annee, couleur) {
    this.marque = marque;
    this.modele = modele;
    this.annee = annee;
    this.couleur = couleur;
    Voiture.nombreDeVoitures++;
}

// Propriété statique pour compter le nombre de voitures
Voiture.nombreDeVoitures = 0;

// Méthodes de la classe Voiture
Voiture.prototype.demarrer = function() {
    console.log(this.marque + " " + this.modele + " démarre.");
};

Voiture.prototype.arreter = function() {
    console.log(this.marque + " " + this.modele + " s'arrête.");
};

Voiture.prototype.accelerer = function(vitesse) {
    console.log(this.marque + " " + this.modele + " accélère à " + vitesse + " km/h.");
};

// Méthode statique pour obtenir le nombre de voitures
Voiture.getNombreDeVoitures = function() {
    return Voiture.nombreDeVoitures;
};
  • Constructeur : __init__ (Python), parenthèses de la classe (Kotlin), constructor (TypeScript), fonction appelée avec new (JavaScript), le nom de la classe (Java).
  • Visibilité : écrite sur chaque membre en Java ; un mot en Kotlin et TypeScript ; inexistante en Python et JavaScript.
  • Propriétés et accesseurs : getters écrits à la main en Java ; champ et accesseur fusionnés en Kotlin et TypeScript ; attributs directement exposés en Python et JavaScript.
  • Mutabilité par défaut : ouvertes en Python et JavaScript ; val en lecture seule en Kotlin ; readonly en option en TypeScript ; en Java, ce que vous voyez ici — encapsulation manuelle.
  • Données de classe : static, @classmethod, companion object, propriété de la fonction — quatre écritures d’une seule idée : le partagé.

Java : introduction globale

  • Orienté objet : le langage encourage la modélisation des problèmes sous forme d’objets.
  • Portable : grâce à la JVM — nous verrons pourquoi dès la prochaine séance.
  • Sûr : typage fort et mécanismes de vérification préventifs.
  • Multi-tâche : l’exécution concurrente de plusieurs fils est prise en charge par le langage.
  • Dynamique : des classes peuvent être chargées pendant l’exécution.
  • Complet : une bibliothèque riche d’API — interfaces graphiques, réseau, bases de données.
  • Le minimum : un éditeur de texte et l’outillage de compilation — installés à la séance « Mise en œuvre de Java ».
  • Gestion de projet et dépendances : Maven, Gradle — vues aux séances dédiées à la construction.
  • IDE : Eclipse (open source, soutenu par IBM), IntelliJ IDEA (JetBrains, versions communautaire et commerciale), Visual Studio Code et ses extensions Java.
  • Avantages : portabilité, sûreté par vérifications, richesse des bibliothèques.
  • Inconvénients : un niveau d’abstraction à l’exécution qui coûte en performance brute ; le code natif est possible, mais au prix de la portabilité.
  • Séance suivante : Java : d’où ça vient, pourquoi c’est comme ça.

Avant-goût : un premier programme

  • Deux objets, un compteur : v1 et v2 sont distincts ; nombreDeVoitures est unique.
  • Le compteur est lu sur la classe, Voiture.getNombreDeVoitures(), et non sur une voiture.
  • Vous compilerez vous-mêmes un programme Java à une séance ultérieure : ici, le code tourne dans le carnet.