Programmation Orientée Objet

La programmation orientée objet (on dira POO) est un paradigme 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

1967 : premier langage orienté objet : Simula 67

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).

 

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 module  math 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)

 

 

 

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_".

 

 

Compléter le programme suivant :
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

 

Correction
class Livre:
    def __init__(self, auteur, titre):
        self.titre = titre
        self.auteur = auteur

    def __repr__(self):
        return f"{self.titre} ({self.auteur})"

livre1 = Livre("Tonino Benacquista", "Malavita")
print(livre1)

# Affichage du titre du livre uniquement
print(livre1.titre)

 

 


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 selfdé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.

 Écrire une fonction 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}\)

 

Écrire une méthode 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()
Rajouter à la classe 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')

 

 

Rajouter à la fonction de construction des instances de 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)

 

Implémenter une méthode 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 ), 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__().

 

Écrire la méthode __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)

 

Faire de même avec les opérateurs suivants :
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 )
Adapter la méthode __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 alors Trueou  False.

 

 

Droite

Il existe plusieurs manières de définir une droite : Point+Vecteur, deux Points, deux Plans, un Point + un Plan…

Créer une classe 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",)et kargs = {p : "3"}

 

Améliorer la fonction distance pour qu’elle admette également un Point et une Droite comme paramètres, et en calcule la distance relative !

 

 

Activités …

Sources :

Vous aimerez aussi...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *