Programmation orientée objet
Introduction :
Dans ce cours nous nous intéressons à la programmation objet sous l’angle des conteneurs de données. Dans une première partie, nous présenterons les principales caractéristiques du paradigme objet, son vocabulaire et sa syntaxe. Nous aborderons dans une deuxième partie la gestion des états internes des objets, puis de leurs fonctionnalités dans une troisième partie.
Principes de la programmation objet
Principes de la programmation objet
Le paradigme objet
Le paradigme objet
L’approche objet permet de créer des structures de données et d’y associer un certain nombre de fonctionnalités. Cette approche fait partie des paradigmes majeurs de programmation.
Un paradigme est un style de programmation , une manière de modéliser les problèmes et d’écrire des programmes pour les résoudre.
La principale caractéristique du paradigme objet est d’associer étroitement les données et le code pour les traiter, selon le principe de l’encapsulation.
Encapsulation :
L’encapsulation désigne le regroupement des données et du code relatif au traitement de ces données.
Cette encapsulation se matérialise par la définition d’une classe d’objets. Celle-ci établit globalement les types de données et y associe des fonctionnalités communes à tous les objets de cette classe.
Certains langages assortissent ce regroupement d’un contrôle très strict de l’accès aux données, mais nous verrons que ce n’est pas le cas en Python.
Les objets individuels sont créés à partir de la classe, par un mécanisme appelé instanciation.
Instanciation :
L’instanciation désigne la création d’un objet appartenant à une classe.
- L’objet devient donc une « instance » de la classe dont il émane.
Les instances partagent des fonctionnalités communes, mais les données sont individuellement propres à chaque objet.
Dans la terminologie objet, les données sont appelées attributs, tandis que les fonctions associées sont appelées méthodes.
Ce cours aborde la programmation objet sous l’angle des structures de données. Dans ce contexte, nous n’étudierons pas les notions d’héritage et de polymorphisme qui appartiennent également au paradigme de programmation objet.
Définition d’une classe
Définition d’une classe
Une classe se définit à partir du mot-clé
suivi du nom de celle-ci. Par convention, les noms de classe commencent par une majuscule.Dans le cas où le nom est composé, on utilise la convention dite « camel case » (« casse de chameau » en français), qui consiste à juxtaposer les mots en mettant leurs premières lettres en majuscules.
Les objets encapsulent des propriétés et des méthodes, comme le montre l’exemple ci-après de définition d’une classe.
Cette portion de code ci-dessus ne fabrique aucun objet. Elle se contente de définir les propriétés et fonctionnalités communes à tous les objets qui appartiendront à cette classe.
Notre classe
définit les méthodes nommées et .La méthode appelée
est le constructeur de nos futurs objets. Elle sera automatiquement appelée au moment de la création des objets de la classe . Nous observons qu’elle requiert :- un paramètre appelé;
- deux autres paramètres qui sont les attributs des élèves, à savoir leur prénom et leur âge.
Les méthodes prennent un premier paramètre désignant l’objet individuel auquel elles s’appliquent. Par convention ce paramètre est appelé
Cette convention varie selon les langages : ce paramètre est ainsi nommé en Javascript, et en PHP.
Sa présence est nécessaire à la définition de la méthode au sein de la classe, mais il ne doit pas être passé en argument lors des créations d’objets ou des appels de leurs méthodes.
La méthode appelée
restitue sous forme de chaîne de caractères les informations définies pour un élève donné. Elle ne requiert aucun paramètre hormis .Instanciation d’objets
Instanciation d’objets
Les objets sont instanciés (autrement dit créés) à partir de la classe à laquelle ils appartiennent.
Les arguments passés pour la création de l’objet « eleve1 » de la classe
sont ceux définis par la méthode , c’est-à-dire son prénom et son âge. Cette méthode est appelée automatiquement pour construire l’objet. Le paramètre implicite ne doit pas être précisé.Appel de méthodes
Appel de méthodes
La syntaxe générale pour l’appel d’une méthode s’effectue selon le format suivant :
.
Si la méthode requiert des paramètres (autres que
qui n’a pas à être spécifié au moment de l’appel), les arguments correspondants doivent être présents entre les parenthèses, de la même manière qu’en programmation fonctionnelle.Les objets de notre classe
disposent d’une méthode que nous pouvons appeler avec la notion pointée.
L’appel de la méthode s’effectue sans passer d’arguments, puisqu’elle est définie sans paramètre autre que
. Le paramètre utilisé dans la déclaration de la fonction au sein de la classe permet de désigner notre objet au sein de la classe. Nous évitons ainsi le recours à une autre syntaxe, possible mais moins compacte, qui oblige à préciser à la fois la classe et le nom de l’instance pour obtenir le même résultat :
À l'instar des fonctions, les parenthèses sont obligatoires pour appeler une méthode, même si on ne lui passe aucun paramètre.
Créons à présent un nouvel objet de la même classe pour un deuxième élève.
Cet exemple montre l’intérêt de la programmation objet : la possibilité de disposer de méthodes communes à des objets distincts dont les valeurs d’attributs peuvent être différentes.
Chaînes de documentation
Chaînes de documentation
Les chaînes de documentation (docstrings en anglais) contribuent, quand elles sont présentes, à informer sur la classe et ses méthodes.
affiche le texte suivant :
Maintenant que ces éléments de vocabulaire et de syntaxe sont précisés, intéressons-nous aux états internes des objets et aux modalités d’accès à ceux-ci.
États internes des objets
États internes des objets
- Le paradigme de programmation fonctionnelle repose sur l’absence d’état des fonctions. Le résultat d’une fonction ne dépend que des arguments fournis en entrée, sans conservation d’états internes une fois le résultat retourné.
- Le paradigme de programmation objet permet pour sa part une conservation d’états internes : les objets contiennent non seulement des fonctions, appelées méthodes, mais aussi des variables, appelées attributs.
- On distingue :
- les variables d’instance ;
- et les variables de classe.
Variables d’instance
Variables d’instance
Les objets peuvent contenir tout type de variable, et ces variables sont accessibles aux méthodes afin que ces dernières puissent les manipuler.
Nous avons vu dans la partie précédente qu’elles pouvaient être définies au moment de l’instanciation de l’objet avec la méthode du constructeur, mais il est possible de définir ou de modifier des variables à tout moment.
Dans l’exemple ci-dessous, nous modifions la méthode pour qu’elle génère les initiales d’un élève quand elle est appelée.
Nous instancions un élève et appelons la méthode qui retourne des informations sur celui-ci.
Les variables des objets, ou attributs d’instance, sont accessibles et modifiables avec la notation pointée.
Contrairement à d’autres langages, Python permet également de déclarer librement des attributs non explicitement définis dans la classe.
Cette faculté permet si on le souhaite d’utiliser une classe comme conteneur de données. La présence de méthodes n’est pas requise pour définir une classe en Python.
Les variables d’instance sont stockées dans l’objet sous forme d’un dictionnaire.
Variables de classe
Variables de classe
Des variables peuvent également être définies au niveau de la classe. Dans ce cas, elles sont communes à tous ses membres, et toute modification d’une variable de classe concernera logiquement l’ensemble des objets qui y sont rattachés.
La syntaxe le reflète avec l’absence de
et le rattachement explicite de la variable à la classe dans les méthodes qui manipulent la variable.Nous l’illustrons avec une classe d’élèves qui comptabilise le nombre d’élèves.
- Le compteur correspondant est incrémenté à chaque fois qu’un élève est ajouté à la classe par instanciation d’un nouvel objet de cette classe.
Nous avons présenté les attributs d’instance et de classe avec des variables de type chaîne de caractères et nombre entiers, mais les autres types de variables sont pareillement utilisables au sein des classes.
Accès aux variables
Accès aux variables
Certains langages obligent à utiliser des méthodes pour lire ou modifier les attributs des objets.
Ces méthodes sont appelées :
- accesseur (ou getter en anglais) pour une méthode permettant de connaître la valeur d’un attribut ;
- mutateur (ou setter en anglais) pour une méthode permettant de modifier un attribut.
Il est possible d’en implémenter en Python, mais cela n’a rien d’obligatoire.
L’utilisation d’accesseurs ou de mutateurs peut présenter un intérêt quand les valeurs doivent faire l’objet d’un traitement particulier. Ou bien pour éviter qu’un·e utilisateur·rice de la classe n’introduise des incohérences, en intervenant sur les contenus des variables, hors du cadre des méthodes implémentées.
On observe que le mutateur applique des traitements à la chaîne de caractère passée en argument :
- transformation si nécessaire de la casse pour mettre en majuscule la première lettre du prénom et les autres en minuscules (méthode title) ;
- suppression d’éventuels espaces avant et après le prénom (méthode strip).
Nous ne développerons pas davantage cet aspect, mais le langage Python propose différents mécanismes spécifiques d’encapsulation plus ou moins forte, avec des attributs privés ou protégés.
Implémentation et interface
Implémentation et interface
L’interface proposée par l’objet peut exposer directement et de manière transparente les structures de données qu’il manipule. Mais cette interface ne reflète pas nécessairement les états ou les types internes des objets.
L’exemple développé ci-après montre un pseudo-dictionnaire implémenté dans une classe :
- dont l’accesseur proposé en interface retourne un dictionnaire ;
- mais dont l’implémentation interne s’appuie sur deux listes mises à jour en parallèle.
Cette implémentation est tout sauf optimale, mais elle illustre la distinction à opérer entre interface et implémentation d’une structure de données.
- L’interface propose une forme d’accès aux données qui n’est pas nécessairement représentative de leur structure interne.
- Cette structure interne peut donc être modifiée tout en conservant à l’identique l’interface.
Nous avons vu le rôle des variables de différents types dans le paradigme objet en tant que structure de données, mais les méthodes que nous allons étudier maintenant contribuent, elles aussi, à l’interface de ces objets.
Fonctionnalités
Fonctionnalités
La programmation objet permet d’implémenter des fonctionnalités, liées aux données qu’elle structure, par l’implémentation de méthodes.
Les méthodes sont des fonctions définies au sein de la classe. Elles peuvent accéder et manipuler les attributs des objets, auxquels peuvent être appliqués différents traitements.
On distingue deux types de méthodes en Python : les méthodes normales et les méthodes spéciales.
Méthodes normales :
Les méthodes normales correspondent, dans le paradigme objet, aux fonctions créées en programmation fonctionnelle. La présence des fonctions dans la définition d’une classe les rend accessibles sous forme de méthodes dans les objets qui y sont rattachés.
Méthodes spéciales :
Les méthodes spéciales se distinguent par leur nommage entouré de doubles tirets bas (underscores en anglais), sous le format
. Elles permettent d’étendre les fonctionnalités des objets en leur conférant des caractéristiques particulières.Nous avons déjà rencontré une méthode spéciale, avec le constructeur
.Quand cette méthode est présente dans la définition de la classe, elle est exécutée automatiquement à la création de chaque objet. Sa présence n’est toutefois pas indispensable pour créer des objets émanant d’une classe.
Il existe un certain nombre de méthodes spéciales qui permettent d’apporter aux objets que nous créons des fonctionnalités particulières. Nous allons l’illustrer en présentant l’implémentation de deux d’entre elles, dont la mission est, respectivement, de mesurer et de comparer des objets.
Longueur d’un objet
Longueur d’un objet
Les objets peuvent disposer de nombreuses caractéristiques, à condition d’implémenter les méthodes correspondantes.
Nous concevons une classe poème destinée à analyser des poésies.
Nous créons une instance avec un poème de l’auteur japonais Matsuo Basho.
Notre objet étant créé, nous souhaitons connaitre sa longueur.
La notion de longueur n’est pas définie pour notre objet.
Nous pourrions obtenir la longueur de la chaîne de caractères nommée
ayant servi à créer notre objet, mais l’idéal serait que cette caractéristique puisse être fournie par l’objet lui-même, en étant appelée en tant que méthode rattachée à l’objet.L’implémentation de la méthode spéciale
va nous permettre d’accéder à cette fonctionnalité. Nous redéfinissons la classe en ajoutant cette méthode qui retourne la longueur de la variable d’instance qui contient le poème :
À nouveau, nous créons une instance à partir de cette nouvelle définition de la classe.
Notre objet étant créé, nous passons la variable qui le référence en argument à la fonction native
.
L’existence de la méthode spéciale
permet à l’interpréteur Python de retourner une longueur pour les objets de la classe ainsi redéfinie.Notre méthode retourne simplement la longueur de la chaîne, mais nous pouvons également définir une autre manière de déterminer cette longueur.
Considérons les deux poèmes suivants :
- Chanson d’automne, de Paul Verlaine ;
- Le Dormeur du val, d’Arthur Rimbaud.
Notre classe implémentant la longueur de manière littérale à partir de la longueur de la chaîne de caractères, nous obtenons les résultats suivants en instanciant les deux objets correspondants :
- Le poème de Verlaine comporte 87 caractères, celui de Rimbaud 185 caractères.
Mais nous pourrions considérer la longueur de ces poèmes différemment, en mesurant plutôt le nombre de lignes qu’ils comportent.
Cette notion serait par exemple utile à un·e éditeur·rice pour la publication d’un recueil, afin de déterminer le nombre de pages de l’ouvrage.
Il nous suffit de redéfinir notre classe afin que la longueur soit désormais définie par le nombre de lignes du poème.
- Nous le déterminons avec la longueur de la liste obtenue en divisant la chaîne de caractères du poème sur la base du caractère spécialde retour à la ligne).
Nous instancions à nouveau deux objets à partir de cette nouvelle définition de notre classe.
Observons à présent leurs longueurs respectives en appelant la fonction native comme précédemment.
- Nous obtenons cette fois 6 et 4, car le poème de Verlaine comporte 6 lignes, et celui de Rimbaud seulement 4.
Le résultat obtenu est très différent de notre précédente implémentation.
Comparaison d’objets
Comparaison d’objets
Les méthodes spéciales nous permettent également d’implémenter des comparaisons entre objets, sous réserve de définir les méthodes correspondantes.
Nous allons l’illustrer avec le principe d’un jeu de lettres privilégiant l’usage de lettres rares pour former des mots. Ce principe sert de base à de nombreux jeux de société.
Nous implémentons ce principe avec une classe
. Cette classe comporte :- une variable de classe, de type dictionnaire, définissant le nombre de points attribués pour l’usage de chacune des lettres de l’alphabet ;
- un constructeur qui convertit le mot proposé en majuscules et conserve uniquement les lettres répertoriées dans le dictionnaire de lettres, et qui en calcule le score par comptage des points.
Nous pouvons calculer les scores des objets, mais pas les comparer entre eux.
affiche TypeError: '<' not supported between instances of 'Mot' and 'Mot'[
affiche TypeError: '<' not supported between instances of 'Mot' and 'Mot'[
Redéfinissions notre classe pour implémenter des méthodes de comparaison entre les instances.
Nous déclarons pour cela les méthodes spéciales
Ces méthodes requièrent deux paramètres, pour désigner l’objet de comparaison et un second paramètre, conventionnellement nommé (« autre »), pour désigner l’objet auquel le premier est comparé.
Le critère de comparaison est librement défini au sein des méthodes correspondantes. Pour les besoins de notre jeu, nous souhaitons effectuer cette comparaison sur la base des scores.
Le résultat obtenu est correct : le score de wok est de 21, il est supérieur à celui de casserole qui est de 10.
- L’expression évaluée est donc fausse.
Nous n’avons pas besoin de définir en miroir les méthodes spéciales complémentaires
(pour greater than, soit « plus grand que ») et (pour greater or equal, soit « plus grand que ou égal à »), pour évaluer la comparaison associée.
Sans avoir recours à une méthode spéciale, il reste possible de comparer le score des deux objets de la manière suivantes :
Nous avons montré un aperçu non exhaustif des méthodes spéciales afin d’illustrer l’intérêt de celles-ci pour enrichir les fonctionnalités des objets. Il est également possible de définir les contenus des méthodes appelées pour la suppression d’un objet, l’addition d’objets entre eux, mais aussi l’itération sur les objets (entre autres).
Nous pouvons ainsi personnaliser le comportement de nos objets en implémentant différentes méthodes spéciales prévues par le langage.
Conclusion :
Les objets constituent un intéressant paradigme de programmation, associant variables et fonctions sous la forme d’attributs et de méthodes. Nous avons décrit le vocabulaire et la syntaxe de la programmation objet. Nous avons ensuite précisé les différents types de variables ou attributs et leurs modalités d’accès. Nous avons enfin montré qu’il était possible de conférer des fonctionnalités particulières aux objets en implémentant les méthodes correspondantes.