Programmation Orientée Objet
La programmation orientée objet (on dira POO) est un
de programmation informatique. Il consiste en la définition et l’interaction de briques logicielles appelées objets ; un objet représente un concept, une idée ou toute entité du monde physique,.exemples : une voiture, une personne, une page d’un livre…
L’objet possède en interne une structure et un comportement, et il sait interagir avec ses pairs.
Il s’agit donc de représenter ces objets et leurs relations ; l’interaction entre les objets via leurs relations permet de concevoir et réaliser les fonctionnalités attendues, de mieux résoudre des problèmes. Dès lors, l’étape de modélisation revêt une importance majeure et nécessaire pour la POO. C’est elle qui permet de transcrire les éléments du réel sous forme virtuelle.
source : Wikipédia
La programmation orientée objet a fait son apparition dans le langage Simula 67 puis SmallTalk. Puis elle s’est développée dans les langages anciens comme le Fortran, le Cobol et est même incontournable dans des langages récents comme Java.
Cette façon d’aborder la programmation possède plusieurs avantages :
- les différents objets peuvent être construits indépendamment les uns des autres (par exemple par des programmeurs différents)
Ce qui permet de :- éviter tout risque d’interférence,
- éviter au maximum l’emploi de variables globales.
- la classification des objets en familles permet de construire de nouveaux objets à partir d’objets préexistants
Ce qui permet de :- réutiliser des pans entiers d’une programmation déjà écrite, pour en tirer une fonctionnalité nouvelle.
Cela est rendu possible grâce aux concepts de polymorphisme et d’héritage (pas au programme de NSI).
- réutiliser des pans entiers d’une programmation déjà écrite, pour en tirer une fonctionnalité nouvelle.
L’objet et la classe
Un objet est un élément d’une classe (en quelque sorte un « type personnalisé » ; il faut entendre type au même titre qu’un nombre, une chaîne de caractères, …).
Définir une classe revient à définir une nouvelle structure de données, qui s’ajoute à celles définies par le langage.
Classe = Type d’objet
Objet = Instance de Classe
Définition de classe
Avant de créer un objet, il faut avoir défini sa classe.
Une classe possède un nom et des membres, c’est à dire des attributs (ou champs) et des méthodes.
Chaque membre d’un objet objet est accessible via l’expression : objet.membre
.
- Les attributs (ou champs) : ils sont à l’objet ce que les variables sont à un programme.
les attributs sont donc typés (exemple int, float, bool, str, … ou n’importe quelle autre classe !)
- Les méthodes sont des procédures ou fonctions destinées à traiter les données. Elles servent d’interface entre les données et le programme.
les méthodes acceptent donc des arguments et peuvent renvoyer des valeurs
Représentation UML
Avec le langage UML (Unified Modeling Language) , on représente une classe sur un diagramme de classe :
Encapsulation
Le fait de réunir dans une même entité (l’objet) les données (attributs) et les routines qui permettent de les manipuler (méthodes) se nomme l’encapsulation.
Cela permet (entre autre) de cacher à l’utilisateur d’un objet certaines données ou procédures internes, l’obligeant à utiliser l’interface prévue par le concepteur de la classe, c’est à dire un ensemble de méthodes publiques.
Encapsulation : Objet = Attributs + Méthodes
En Python
En Python, tout est objet !!
Par exemple le modulemath
est un objet, et possède donc :
- des attributs :
pi
, …- des méthodes :
cos()
, …
En Python, on définit une classe grâce à l’instruction class
:
class NomDeLaClasse: """<bloc de définition de la classe>""" pass
Remarque : par convention, les classes doivent être nommées en CamelCase : une suite de mots, sans espace ni séparateur, dont la première lettre est une capitale.
Le bloc de définition d’une classe contient :
- des attributs de classe (voir plus bas …)
- des méthodes :
- des méthodes spéciales : leurs noms, réservés par le langage Python, commencent et finissent par « __ » : deux tirets « bas »
exemples :__init__
,__repr__
- des méthodes personnalisées
- des méthodes privées (inaccessibles en dehors des méthodes de l’objet lui-même) : leurs noms commencent par
"__"
: deux tirets « bas » (underscore)
- des méthodes spéciales : leurs noms, réservés par le langage Python, commencent et finissent par « __ » : deux tirets « bas »
Instanciation d’objet
Une fois la classe définie, on peut créer autant d’objets de cette classe (on parle d’instances) que l’on veut :
mon_1er_objet = NomDeLaClasse() mon_2eme_objet = NomDeLaClasse() ...
L’opération qui permet de créer l’objet s’appelle la construction.
À chaque construction d’un objet, la méthode __init__()
de la classe est exécutée. On appelle parfois cette méthode : constructeur.
Cette méthode permet en général de définir les différents attributs de l’objet :
class NomDeLaClasse: def __init__(self, attr1, attr2 = 0): self.attribut1 = attr1 # attribut1 est un attribut de l'objet (self) self.attribut2 = attr2 # attribut2 est un attribut de l'objet (self) variable1 = 1 # variable n'est pas un attribut de l'objet (c'est une simple variable local de la méthode __init__)
Les règles de définition des méthodes sont identiques à celles de définition des fonctions, hormis pour le premier paramètre,
self
: il désigne l’objet lui-même et lors de l’appel de la méthode, il est fourni devant le nom de la méthode et non entre les parenthèses.
… et la construction de l’objet permet de leur attribuer des valeurs :
mon_1er_objet = NomDeLaClasse("Salut") print(mon_1er_objet.attribut1) # affiche 'Salut' (argument passé à la méthode "constructeur") print(mon_1er_objet.attribut2) # affiche 0, sa valeur par défaut (car pas passé à la méthode "constructeur")
Attributs d’instance / Attributs de classe
Chaque objet peut contenir ses propres valeurs d’attributs, ce sont les attributs d’instance.
Mais on peut également attribuer des attributs à la classe elle-même, de sorte que tous les objets de cette classe puissent les utiliser. On parle d’attribut de classe (static en Java ou en C++), et en Python, on les définit au niveau du bloc de définition de la classe :
class NomDeLaClasse: attribut_0 = 100 # attribut de classe def __init__(self, attr1, attr2 = 0): self.attribut1 = attr1 # attribut d'instance self.attribut2 = attr2 + NomDeLaClasse.attribut_0
Accès à un attribut de la classe NomDeLaClasse
:
print(NomDeLaClasse.attribut_0) # affiche 100
Accès aux attributs de différentes instances de la classe NomDeLaClasse
:
mon_1er_objet = NomDeLaClasse("Bonjour") print(mon_1er_objet.attribut1) # affiche 'Bonjour' print(mon_1er_objet.attribut2) # affiche 100
Méthodes
Les méthodes sont des fonctions encapsulées dans la classe d’un objet : on les appelle toujours à partir d’une instance de la classe.
Fonction :
# Au niveau principal du programme/module : def addition(a, b): return a+b # Appel de la fonction : print(addition(2, 3))
|
Méthode :
class NomDeLaClasse: .... # Au niveau du bloc de définition de la classe : def addition(self, b): return self + b mon_objet = NomDeLaClasse() # instance de la classe NomDeLaClasse # Appel de la méthode : mon_objet.addition(3)
|
Variables privées, accesseurs et mutateurs
Afin d’éviter des actions non-désirées sur les attributs d’un objet, on choisit souvent de les rendre privés : cela signifie qu’il est interdit de les lire ou les modifier directement depuis l’objet. Seuls deux méthodes (l’accesseur et le mutateur) constituent l’interface entre l’objet et ces attributs.
De la même manière, il arrive que certaines méthodes doivent rester privées et n’ont pas vocation à être appelées depuis l’extérieur.
En Python, pour définir une variable privée (attribut ou méthode), il suffit de faire précéder son nom par 2 caractères underscore
"__"
.
Exemple :
class MaClasse: def __init__(self, a): self.__a = a a = MaClasse(1) print(a.__a)
Après exécution :
AttributeError: 'MaClasse' object has no attribute '__a'
Un accesseur (on dit aussi getter) est une méthode permettant d’obtenir la valeur d’un attribut.
Une convention de nommage veut que l’on fasse commencer le nom de l’accesseur par le préfixe
"get_"
.
Un mutateur (on dit aussi setter) est une méthode permettant de modifier la valeur d’un attribut.
Une convention de nommage veut que l’on fasse commencer le nom du mutateur par le préfixe
"set_"
.
class Livre: def __init__(self, ........): self.titre = .... self.auteur = .... def __repr__(self): return f"...." livre1 = Livre("Tonino Benacquista", "Malavita") print(livre1) # Affichage du titre du livre uniquement print(.....)
Son exécution doit afficher :
Malavita (Tonino Benacquista) Malavita
Un peu de géométrie …
Le point
Soit \(M\) un point du plan \(\left(\vec{x}, \vec{y}\right)\) et de coordonnées cartésiennes \(x_M\) et \(y_M\) dans un repère \(\mathrm{R}\left(O, \vec{x}, \vec{y}\right)\).
On peut considérer le point comme un objet géométrique, possédant 2 attributs : ses coordonnées dans \(\mathrm{R}\).
Définition de classe en Python :
class Point: def __init__(self, x, y): # méthode "constructeur" self.x = x # self.y = y # attributs: coordonnées
Remarque : le paramètre
self
désigne l’objet lui même. On utilise ce terme à l’intérieur de la classe (arguments et méthodes) pour éviter les ambiguïtés lorsque l’on s’intéresse aux membres de l’objet (self.x
= « c’est mon x »).
>>> M = Point(1, 3) >>> M <__main__.Point object at 0x004D4910>
Si on souhaite afficher des informations plus explicites sur le point, on peut déclarer, à l’intérieur de la déclaration de classe, la méthode __repr__()
(il s’agit d’un nom réservé, d’où les deux caractères « _ ») :
def __repr__(self): # méthode pour l'affichage return "P(%.4f, %.4f)" %(self.x, self.y)
Cette méthode n’attend aucun autre argument que l’objet lui même (
self
) et doit renvoyer une chaîne de caractères.
Ce qui aura pour conséquence le comportement suivant :
>>> M P(1, 3)
Remarque : toutes les méthodes ou attributs commençant par « __ » (deux tirets-bas) sont privés, c’est à dire qu’ils ne peuvent être utilisés que depuis l’intérieur même de l’objet (c’est à dire par les méthodes de l’objet).
On peut créer ses propres membres privés (en plus des noms déjà réservés) pour mieux contrôler la modification des données de l’objet.
distance(p1, p2)
admettant 2 objets de type Point
comme paramètres et renvoyant la valeur de la distance entre ces deux points.Rappel : la distance entre deux points \(A\begin{pmatrix}x_A \\ y_A \\\end{pmatrix}\) et \(B\begin{pmatrix}x_B \\ y_B \\\end{pmatrix}\) s’exprime
\(D=\sqrt{\left(x_B-x_A\right)^2+\left(y_B-y_A\right)^2}\)
distance(self, p)
admettant 2 objets de type Point
(dont l’objet lui-même) comme paramètres et renvoyant la valeur de la distance entre ces deux points.
Pour afficher ce point graphiquement on peut utiliser le module pylab
:
import pylab # tracé d'un simple point : pylab.plot(x, y, 'o') # 'o' est un code de marqueur (voir https://matplotlib.org/stable/api/markers_api.html) # pour placer toute la figure dans un repère orthonormé : pylab.axis('scaled') # affichage de la figure : pylab.show()
Point
une méthode dessiner
permettant d’afficher graphiquement les instances de Point
dans un espace orthonormé.Ainsi, si on exécute les commandes suivantes :
p1 = Point(4, 4) p1.dessiner() p2 = Point(2, 5) p2.dessiner() p3 = Point(-1, 2) p3.dessiner()
On doit voir apparaitre :
La fonction pylab.plot
accepte également un 3ème argument facultatif permettant de spécifier le marqueur et la couleur du tracé. Cette information peut être donnée sous la forme d’un simple caractère, parmi ‘b’, ‘g’, ‘r’, ‘c’, ‘m’, ‘y’, ‘k’, ‘w’.
Exemple pour tracer un point rond de couleur rouge :
pylab.plot(x, y, 'or')
Point
un paramètre facultatif c
, de valeur par défaut égale au caractère "k"
.Ainsi, si on exécute les commandes suivantes :
p1 = Point(4, 4, 'r') p1.dessiner() p2 = Point(2, 5, 'y') p2.dessiner() p3 = Point(-1, 2, 'c') p3.dessiner()
On doit voir apparaitre :
Le vecteur
De même que pour le point, on peut définir un vecteur par ses 2 coordonnées dans le repère \(\mathrm{R}\).
Définition de classe en Python :
class Vecteur: def __init__(self, x, y): # méthode "constructeur" self.x = x # self.y = y # attributs : coordonnées
Et pour en tracer un représentant dans le plan, on utilise la fonction pylab.quiver
:
pylab.quiver(x0, y0, x, y, units='xy' ,scale=1)
dessiner
, admettant un seul paramètre p
(en plus de l’objet lui même bien sûr) de type Point
et permettant d’afficher un représentant de ce vecteur au point p
.Ainsi, si on rajoute les commandes suivantes aux précédentes :
v1 = Vecteur(1,2) v1.dessiner(p3) v2 = Vecteur(-3,-1) v2.dessiner(p1) v2.dessiner(p2)
On doit voir apparaitre :
Parmi les noms de méthodes spéciales (elles sont toutes là), il en existe une permettant d’utiliser le symbole « + » pour faire une « somme » de deux objets, de mêmes types ou pas : la méthode __add__()
.
__add__
, acceptant comme argument (en plus du vecteur lui-même, self
) un autre vecteur v
, et en renvoyant le vecteur somme.Le résultat doit permettre de faire ça (après avoir implémenté une méthode __repr__()
comme pour le point) :
>>> u = Vecteur(1, 5) >>> v = Vecteur(1, 4) >>> u + v v(2, 9)
nom de la méthode | expression Python | écriture mathématique | |||
différence : | __sub__ |
: | u - v |
⇔ | \(\vec{u}-\vec{v}\) |
norme : | __abs__ |
: | abs(u) |
⇔ | \(\|\vec{u}\|\) |
négation : | __neg__ |
: | -u |
⇔ | \(-\vec{u}\) |
produit par un scalaire : | __mul__ |
: | k*u |
⇔ | \(\vec{u}\times k\) |
(dans ce dernier cas, les arguments étant de types différents, il faut aussi définir la fonction « réfléchie » __rmul__ , pour pouvoir faire k*u et u*k ) |
__mul__
pour qu’elle puisse également réaliser un produit scalaire.Pour tester si un objet est une instance d’une classe donnée, on peut utiliser la fonction
isinstance(objet, NomDeLaClasse)
, qui renvoie alorsTrue
ouFalse
.
Droite
Il existe plusieurs manières de définir une droite : Point+Vecteur, deux Points, deux Plans, un Point + un Plan…
Droite
, dont le constructeur peut admettre différents types d’arguments (pourvu que ceux-ci puissent permettre la définition univoque de l’objet Droite
).
Pour passer à une fonction des arguments en quantité non prédéterminée (de 0 à plusieurs), on peut utiliser l’opérateur « * » :
- l’argument
*args
dans la définition de la fonction va récupérer tous les arguments donnés lors de l’appel de la fonction dans un tuple nomméargs
.exemple :
def mafct(*args)
→mafct("a", 2)
→args = ("a", 2)
- l’argument
**kargs
dans la définition de la fonction va récupérer tous les arguments donnés lors de l’appel de la fonction dans un dictionnaire nommékargs
.exemple :
def mafct(**kargs)
→mafct(k="z", p=3)
→kargs = {'k': "z", 'p': 3}
- et on peut combiner les deux :
exemple :
def mafct(*args, **kargs)
→mafct("a", p="3")
→args = ("a",)
etkargs = {p : "3"}
distance
pour qu’elle admette également un Point
et une Droite
comme paramètres, et en calcule la distance relative !
Activités …
Sources :