Aller au contenu

Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.

Paradigme Objet (Bac 🎯)⚓︎

programme

Sources et crédits pour ce cours

Pour préparer ce cours, j'ai utilisé :

🔖 Synthèse de ce qu'il faut retenir pour le bac

Concepts de la Programmation Orientée Objet (POO)⚓︎

Point de cours 1

Le paradigme de Programmation Orientée Objet (POO) propose une organisation du code autour du concept d'objet.

Un objet regroupe pour une même structure de données :

  • des informations stockées sous forme d'attributs qui sont des variables
  • les fonctions permettant de manipuler ces informations sous forme de méthodes qui sont des fonctions

Chaque objet est fabriqué à l'aide d'une classe. Une classe est un nouveau type de données qui est défini par le programmeur.

La classe est le moule de l'objet, un même moule peut servir à fabriquer plusieurs objets. On dit qu'un objet est une instance de sa classe.

Point de cours 2

En regroupant tout le code d'une structure de données dans une classe, le paradigme de Programmation Orientée Objet (POO) permet :

  • d'améliorer la lisibilité du code
  • de cloisonner les espaces de nommage : un même nom de méthode ou d'attribut peut être utilisé dans plusieurs classes
  • de faciliter la maintenance et la réutilisatibilité du code en implémentant le principe d'encapsulation : on n'a pas besoin de connaître les détails d'implémentation interne d'un objet pour l'utiliser, l'interface publique offerte par ses méthodes doit suffir. L'application stricte de ce principe conduit à distinguer des niveaux d'accès public (depuis l'extérieur de la classe) ou privé (depuis l'intérieur de la classe) pour les attributs et méthodes d'une classe. C'est le cas en Java mais pas en Python.

Exercice 3

Voici une définition en pseudo-code d'une classe Voiture avec trois attributs et trois méthodes. Il faut évidemment créer l'objet avant de l'initialiser avec le constructeur. La syntaxe va dépendre du langage. On donne des traductions en Python et en Java.

🐍 Script Python
Classe Voiture:

Attributs:
    - marque: Chaîne de caractères
    - couleur: Chaîne de caractères
    - vitesse: Entier

Méthode constructeur(objet, marque, couleur):
    # Initialise un objet de la classe Voiture
    objet.marque = marque
    objet.couleur = couleur
    objet.vitesse = 0

Méthode accelerer(objet, increment):
    # Méthode pour augmenter la vitesse de la voiture
    objet.vitesse = objet.vitesse + increment

Méthode freiner(self, decrement):
    # Méthode pour réduire la vitesse de la voiture
    objet.vitesse = objet.vitesse - decrement
🐍 Script Python
class Voiture:

    def __init__(self, marque, couleur):
        """Constructeur de la classe Voiture"""
        # attributs
        self.marque = marque
        self.couleur = couleur
        self.vitesse = 0
    
    def accelerer(self, increment):
        """Méthode pour augmenter la vitesse de la voiture"""
        self.vitesse += increment
    
    def freiner(self, decrement):
        """Méthode pour réduire la vitesse de la voiture"""
        self.vitesse -= decrement

# Code client
v = Voiture("Renault", "blanche")
v.accelerer(10)
Java
public class Voiture {
    // Attributs
    private String marque;
    private String couleur;
    private int vitesse;
    
    // Constructeur
    public Voiture(String marque, String couleur) {
        this.marque = marque;
        this.couleur = couleur;
        this.vitesse = 0;
    }
    
    // Méthode pour accélérer la voiture
    public void accelerer(int increment) {
        vitesse += increment;
    }
    
    // Méthode pour freiner la voiture
    public void freiner(int decrement) {
        vitesse -= decrement;
    }
    
    // Code client de test
    public static void main(String[] args) {
        Voiture maVoiture = new Voiture("Renault", "blanche") ;
        maVoiture.accelerer(10);
    }
}        

Question 1

Lorsqu'on définit une classe, quel mot clef spécifique désigne l'objet, ou instance de classe, auquel s'applique un attribut ou une méthode, en Python ? et en Java ?

Dans le code de la classe, l'objet, ou instance de classe, est désigné par self en Python et par this en Java.

Quand on écrit v.accelerer(10), la méthode appeler est bien appelée sur l'objet v avec deux paramètres : l'objet lui-même v et la vitesse 10.

Question 2

Quelle(s) différence(s) peut-on noter entre Python et Java lors de la création d'une instance de classe ?

Python :

🐍 Script Python
v = Voiture("Renault", "blanche")

Java :

Java
Voiture maVoiture = new Voiture("Renault", "blanche") ;

En Java on a en plus le type Voiture qui préfixe la variable lors de sa création et le mot clef new qui permet de créer l'instance de classe avant de l'initialiser avec le constructeur Voiture.

Question 3

Peut-on modifier directement la marque de la voiture en Java avec maVoiture.marque = "Peugeot"; et en Python avec v.marque = "Peugeot" ?

C'est impossible en Java car l'attribut marque est privé. C'est possible en Python où le principe d'encapsulation n'est pas appliqué de façon stricte. Néanamoins on peut marquer des attributs ou méthodes comme privés en les préfixant d'un underscore, libre à l'utilisateur de la classe de respecter cette convention.

Exercice 4

🎯 Compréhension des concepts

Cet exercice est un QCM. Vous devez cocher la ou les bonne(s) réponse(s) par question.

  1. Question 1 : Quel est l'avantage principal de la programmation orientée objet par rapport à la programmation procédurale ?

    • Une meilleure utilisation de la mémoire
    • Une meilleure lisibilité du code
    • Une exécution plus rapide des programmes
    • Une plus grande facilité de maintenance du code
  2. Question 2 : Quel est le principe fondamental de l'encapsulation en programmation orientée objet ?

    • La réutilisation du code existant
    • La limitation de l'accès direct aux données internes d'un objet
    • La création de nouvelles classes à partir de classes existantes
    • L'association de données et de comportements dans une même entité
  3. Question 3 : Quelle est la relation entre une classe et un objet dans la programmation orientée objet ?

    • Une classe est un objet
    • Un objet est une instance d'une classe
    • Une classe hérite d'un objet
    • Un objet est un conteneur de classes
  1. Question 1 : Quels sont les principaux avantages de la programmation orientée objet par rapport à la programmation procédurale ?

    • ❌ Une meilleure utilisation de la mémoire
    • ✅ Une meilleure lisibilité du code
    • ❌ Une exécution plus rapide des programmes
    • ❌ Une plus grande facilité de maintenance du code
  2. Question 2 : Quel est le principe fondamental de l'encapsulation en programmation rientée objet ?

    • ❌ La réutilisation du code existant
    • ✅ La limitation de l'accès direct aux données internes d'un objet
    • ❌ La création de nouvelles classes à partir de classes existantes
    • ❌ L'association de données et de comportements dans une même entité
  3. Question 3 : Quelle est la relation entre une classe et un objet dans la programmation orientée objet ?

    • ❌ Une classe est un objet
    • ✅ Un objet est une instance d'une classe
    • ❌ Une classe ne peut fabriquer qu'un seul objet
    • ❌ Un objet est un conteneur de classes

Interface et implémentation en Python⚓︎

Point de cours 3

Une classe est déterminée par son interface :

  • la liste des attributs avec leur type et leur signification
  • la liste des méthodes avec leur signature et leur spécification

Méthode 1

Voici l'exemple de l'interface d'une classe Point permettant de créer des objets représentants des points du plan.

  • Attributs :
Nom de l'attribut Type Signification
x float abscisse du point
y float ordonnée du point
  • Méthodes :
Nom de la méthode signature Sprécification
__init__ __init__(self, x, y) construit un point de coordonnées x et y
distance distance(self, autre) distance entre le point courant et un autre point

On donne ci-dessous une implémentation en Python de cette interface.

Dans la syntaxe, on distingue les phases de définition de la classe et de manipulation d'un objet instancié.

Définition de la classe

Action Syntaxe
Définition d'une classe class Maclasse:# bloc indenté
Référence à l'objet courant depuis l'intérieur de la classe self
Définition d'une méthode comme une fonction, self obligatoire comme premier paramètre def methode(self, paramètre): # bloc
Initialisation des attributs dans la méthode spéciale __init__
Accès à un attribut depuis l'intérieur de la classe self.attribut

Instanciation et manipulation d'un objet

On crée ou instancie un objet en utilisant le nom de la classe comme une fonction à laquelle on passe les valeurs par défaut des attributs. L'objet est créé et la méthode spéciale __init__ est appelée pour initialiser les attributs.

On manipule ensuite les attributs comme des variables et les méthodes comme des fonctions avec la notation pointée objet.attribut ou objet.methode(paramètres).

Action Syntaxe
Instanciation/Création d'un objet objet = Maclasse(valeurs_attributs)
Appel de méthode sur l'objet objet.methode(paramètres)
Accès aux attributs depuis l'extérieur de la classe objet.attribut

💡 Si on veut respecter le principe d'encapsulation, il ne faut pas accéder directement aux attributs mais le faire à travers des méthodes appelées getter en lecture et setter en écriture.

L'affichage par défaut d'un objet n'est pas explicite c'est pourquoi on peut vouloir définir une méthode spéciale __str__ pour l'affichage qui sera appelée de façon simplifiée avec str(objet) (voir Méthodes spéciales) :

🐍 Script Python
>>> p1 = Point(10, 4)
>>> p1
<__main__.Point at 0x7f42b8f21a30>

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : /∞

Exercice 5

On reprend l'exemple précédent.

  1. Comment peut-on construire un point p2 de coordonnées \((3, 4)\) ?
  2. Comment peut-on calculer la distance entre p1 et p2 ?
  1. Pour construire un point p2 de coordonnées \((3, 4)\) :
    🐍 Script Python
    p2 = Point(3, 4)
    
  2. Pour calculer la distance entre p1 et p2 :
    🐍 Script Python
    d = p1.distance(p2)
    
    ou
    🐍 Script Python
    d = p2.distance(p1)
    

Manipuler ou faire interagir des objets avec des méthodes⚓︎

Exercice 6

On reprend les classes Point et Vecteur définies précédemment.

On a du changer le nom de la classe Point en Point1 car toutes les consoles de cette page sont dans le même espace de nommage et on ne peut pas avoir deux classe portant le même nom !

Compléter les méthodes ci-dessous de sorte que la spécification et les tests inclus dans leurs docstring soient vérifiés :

  • méthode translation de la classe Point
  • méthode égalité de la classe Point
  • méthode mult_scal de la classe Vecteur
  • méthode égalité de la classe Vecteur

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : /∞

🐍 Script Python
import math
import doctest

class Vecteur:
    """Classe de fabrication d'un vecteur du plan"""
    
    def __init__(self, x, y):
        """
        Constructeur d'un vecteur à partir de ses coordonnées
        >>> v1 = Vecteur(-2, 5)
        """
        self.x  = x  # attribut x
        self.y = y   # attribut y 
    
    def addition(self, autre):
        """Méthode d'addition à un autre vecteur"""
        xa = autre.x
        ya = autre.y
        return Vecteur(self.x + autre.x, self.y + autre.y)
    
    def égalité(self, autre):
        """Méthode renvoyant un booléen déterminant 
        si les vecteurs self et autre sont égaux
        """
        # à compléter
        return self.x == autre.x and self.y == autre.y
    
    
    def mult_scal(self, k):
        """
        Méthode qui renvoie un nouveau vecteur k * self k scalaire
        >>> v1 = Vecteur(-2, 5)
        >>> v2 = v1.mult_scal(3)
        >>> v3 = Vecteur(-6, 15)
        >>> v2.égalité(v3)
        True
        """
        # à compléter
        return Vecteur(self.x * k, self.y * k)


class Point1:
    """Classe de fabrication d'un point du plan"""
    
    def __init__(self, x, y):
        """Constructeur d'un point à partir de ses coordonnées
        >>> p1 = Point1(0, 0)
        """
        self.x = x
        self.y = y
        
    def distance(self, autre):
        """Méthode qui renvoie la distance d'un point à un autre point"""
        return math.sqrt((self.x - autre.x) ** 2 + (self.y - autre.y) ** 2)
    
    def translation(self, vect):
        """Méthode qui renvoie le point obtenu à partir de self
        par translation de vecteur vect"""
        # à compléter
        vx = vect.x
        vy = vect.y
        return Point1(self.x + vx, self.y + vy)
    
    def égalité(self, autre):
        """Méthode renvoyant un booléen déterminant 
        si les points self et autre sont égaux
        >>> p1 = Point(3, 4)
        >>> v1 = Vecteur(-2, 5)
        >>> p2 = p1.translation(v1)
        >>> p3 = Point1(1, 9)
        >>> p2.égalité(p3)
        True
        """
        # à compléter
        return self.x == autre.x and self.y == autre.y

# On exécute les tests inclus dans les docstrings
# à décommenter
doctest.testmod(verbose=True)

Remarque 1

On pourrait imaginer que pour tester l'égalité de deux objets de la même classe, il suffirait d'écrire objetA == objetB.

⚠️ Pour les objets définis par le programmeur, le comportement de l'opérateur == n'est pas le même que pour les types built-in1 de Python. Le test ne s'applique pas aux valeurs des attributs mais uniquement aux identifiants des objets, c'est-à-dire leur adresse mémoire. Deux objets qui ont été instanciés de façon séparée sont considérés comme différents même s'ils ont les mêmes valeurs d'attributs. Dans ce cas deux objets ne sont égaux que si l'un est un alias c'est-à-dire qu'ils partagent la même référence.

Néanmoins on peut modifier le comportement par défaut de l'opérateur == en le personnalisant pour la classe que l'on définit. Nous le verrons dans le paragraphe sur les méthodes spéciales.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : /∞

Création d'une classe à partir de son interface⚓︎

Exercice 7

Vous développez un logiciel de gestion de compte pour une banque et vous devez écrire deux classes Client et Compte dont on donne les interfaces ci-dessous :

Classe Client

  • Attributs :
Nom de l'attribut Type Signification
nom str nom du client
prénom str prénom du client
mail str adresse email du client
gestion dict dictionnaire associant à un nom de compte, un objet de la classe Compte
  • Méthodes :
Nom de la méthode signature Sprécification
__init__ __init__(self, enreg) construit un client à partir de enreg dictionnaire de clefs "nom", "prénom", "mail"
ajoute_compte ajoute_compte(self, compte) ajoute compte au dictionnaire self.gestion

Classe Compte

  • Attributs :
Nom de l'attribut Type Signification
nom str nom du compte
plafond int plafond en euros du compte
solde float solde en euros du compte
  • Méthodes :
Nom de la méthode signature Sprécification
__init__ __init__(self, nom, plafond) construit un compte à partir de son nom et de son plafond, le solde est initialisé à 0
deposer deposer(self, somme) ajoute somme à self.solde sauf si dépassement du plafond, ne renvoie rien
retirer retirer(self, somme) enlève somme si self.solde >= somme et renvoie somme
valeur_solde valeur_solde(self, somme) renvoie la valeur self.solde

On donne ci-dessous des exemples d'application attendues.

🐍 Script Python
>>> fred = Client({"nom": "Dard", "prénom": "Frédéric", "mail": "fred69@gmail.com"})
>>> plafond_livretA = 22950
>>> livretA_fred = Compte("Livret A", plafond_livretA )
>>> fred.ajoute_compte(livretA_fred)
>>> print("Attribut : fred.nom | ", "Type :  ", type(fred.nom), "| Valeur : ", fred.nom)
Attribut : fred.nom |  Type :   <class 'str'> | Valeur :  Dard
>>> print("Attribut : fred.prénom | ", "Type :  ", type(fred.prénom), "| Valeur : ", fred.prénom)
Attribut : fred.prénom |  Type :   <class 'str'> | Valeur :  Frédéric
>>> print("Attribut : fred.mail | ", "Type :  ", type(fred.mail), "| Valeur : ", fred.mail)
Attribut : fred.mail |  Type :   <class 'str'> | Valeur :  fred69@gmail.com
>>> print("Attribut : fred.gestion | ", "Type :  ", type(fred.gestion), "| Valeur : ", fred.gestion)
Attribut : fred.gestion |  Type :   <class 'dict'> | Valeur :  {'Livret A': <__main__.Compte object at 0x7f6bef7cb880>}
>>> livretA_fred.deposer(200)
'Dépôt  de 200 euros effectué'
>>> livretA_fred.valeur_solde()
200
>>> livretA_fred.retirer(250)
'Retrait impossible, solde insuffisant'
>>> livretA_fred.retirer(150)
'Retrait  de 150 euros effectué'
>>> livretA_fred.valeur_solde()
50

Dans l'IDE ci-dessous écrire des classes Client et Compte implémentant leurs interfaces et permettant de retrouver les applications ci-dessus.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : /∞

🐍 Script Python
class Client:

def __init__(self, enreg):
    self.nom = enreg["nom"]
    self.prénom = enreg["prénom"]
    self.mail = enreg["mail"]
    self.gestion = dict()
    
def ajoute_compte(self, c):
    self.gestion[c.nom] = c
    
class Compte:
    
    def __init__(self, nom, plafond):
        self.nom = nom
        self.plafond = plafond
        self.solde = 0
        
    def deposer(self, somme):
        if  somme < 0:
            return "On doit déposer une somme > 0"
        nouveau = self.solde + somme
        if nouveau > self.plafond:
            return f"Dépôt impossible dépassement du plafond de {plafond} euros"
        self.solde = nouveau
        return f"Dépôt  de {somme} euros effectué"
    
    def retirer(self, somme):
        if somme < 0:
            return "On doit retirer une somme > 0"
        nouveau = self.solde - somme
        if nouveau < 0:
            return f"Retrait impossible, solde insuffisant"
        self.solde = nouveau
        return f"Retrait  de {somme} euros effectué"
    
    def valeur_solde(self):
        return self.solde

Aller plus loin avec les méthodes spéciales (hors programme)⚓︎

Méthode 2

  • On a déjà rencontré la méthode spéciale __init__ qui permet de construire (= créer et initialiser) un objet. Elle est spéciale dans le sens où on ne l'appelle pas directement mais à travers une syntaxe simplifiée d'appel de fonction avec le nom de la classe.

  • On a vu également que le test d'égalité avec l'opérateur == renvoie False pour deux objets qui n'ont pas le même identifiant mémoire mais qui ont les mêmes valeurs pour tous leurs attributs. On peut étendre l'interface de la classe avec une méthode égalité mais Python permet d'aller plus loin en définissant une méthode spéciale __eq__ qui sera appelée lorsque l'opérateur == sera utilisé pour comparer deux objets de cette classe. Ainsi on retrouvera le comportement attendu :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : /∞

  • Il existe un certain nombre de méthodes spéciales dont l'appel sera effectué avec une fonction ou un opérateur built-in de Python. Il suffit de les définir dans la classe pour manipuler ses objets avec les mêmes fonctions ou opérateurs que les types built-in de Python.
Méthode spéciale Appel Effet
__eq__(self, autre) objet == autre Compare objet et autre de la même classe
__str__(self) str(objet) Renvoie une chaîne de caractères représentant l'objet (valeur affichée par print)
__repr__(self) repr(objet) Renvoie une chaîne de caractères permettent de construire l'objet (évaluation de l'objet dans la console)
__lt__(self, autre) objet < autre Renvoie un booléen indiquant si objet inférieur à autre selon l'ordre choisi

On peut définir bien d'autres méthodes spéciales si cela fait sens pour manipuler les objets de la classe.

Méthode spéciale Appel Effet
__add__(self, autre) objet + autre Addition de deux objets, renvoie un nouvel objet
__len__(self) len(objet) Renvoie la taille de l'objet
__contains__(self, element) element in objet Teste si element dans objet

On a étendu ci-dessous l'interface de notre classe Point avec quelques méthode spéciales :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : /∞


  1. Le module builtins est chargé par défaut dans l'interpréteur Python, il offre l'accès à des fonctions comme len, max, sum etc ... et aux opérateurs arithmétiques +, * etc ... et de comparaison ==, <= etc ...