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 🎯)⚓︎
Sources et crédits pour ce cours
Pour préparer ce cours, j'ai utilisé :
- le cours de Gilles Lassus
- le cours de Franck Chambon
- le manuel NSI chez Ellipses de Balabonski, Conchon, Filliâtre, Nguyen
- le manuel NSI chez Hachette sous la direction de Michel Beaudouin Lafon
- le cours de mon collègue Pierre Duclosson
🔖 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.
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
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)
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 :
v = Voiture("Renault", "blanche")
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.
-
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
-
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é
-
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
-
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
-
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é
-
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 etsetter
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) :
>>> p1 = Point(10, 4)
>>> p1
<__main__.Point at 0x7f42b8f21a30>
Exercice 5
On reprend l'exemple précédent.
- Comment peut-on construire un point
p2
de coordonnées \((3, 4)\) ? - Comment peut-on calculer la distance entre
p1
etp2
?
- Pour construire un point
p2
de coordonnées \((3, 4)\) :🐍 Script Pythonp2 = Point(3, 4)
- Pour calculer la distance entre
p1
etp2
:🐍 Script Pythonoud = p1.distance(p2)
🐍 Script Pythond = 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
enPoint1
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 classePoint
- méthode
égalité
de la classePoint
- méthode
mult_scal
de la classeVecteur
- méthode
égalité
de la classeVecteur
# Tests
(insensible à la casse)(Ctrl+I)
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-in
1 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.
# Tests
(insensible à la casse)(Ctrl+I)
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.
>>> 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.
# Tests
(insensible à la casse)(Ctrl+I)
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
==
renvoieFalse
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 :
# Tests
(insensible à la casse)(Ctrl+I)
- 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 typesbuilt-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 :
# Tests
(insensible à la casse)(Ctrl+I)
-
Le module
builtins
est chargé par défaut dans l'interpréteur Python, il offre l'accès à des fonctions commelen
,max
,sum
etc ... et aux opérateurs arithmétiques+
,*
etc ... et de comparaison==
,<=
etc ... ↩
# Tests
(insensible à la casse)(Ctrl+I)