Concepts de base de la programmation orientée objet
Temps de lecture20 minEn bref
Résumé de l’article
Dans cet article, nous présentons les concepts de base liés à la Programmation Orientée Objet (POO). En particulier, nous étudions les concepts de classes et d’objets et la manière dont ils sont liés les uns aux autres, puis nous abordons la notion d’héritage.
Points clés
-
La programmation orientée objet (POO) est un paradigme de programmation basé sur le concept d’objets, qui peuvent contenir des données et du code : des données sous forme de champs (appelés attributs ou propriétés), et du code sous forme de fonctions/procédures (appelées méthodes).
-
Les programmes informatiques orientés objets sont conçus comme des objets qui interagissent les uns avec les autres.
-
Un objet est une abstraction (logicielle) d’une entité du monde réel impliquée dans l’exécution d’un programme. Il est composé d’une identité, d’un état et d’un comportement.
-
Une classe est une entité qui définit la structure (état ou attributs) et le comportement (méthodes) d’une famille d’objets. Elle se compose d’un nom, d’attributs, de méthodes et d’un ou plusieurs constructeurs. Un constructeur est une méthode particulière dont le but est de créer un objet.
-
L’encapsulation est le principe qui consiste à cacher ou à protéger (une partie) de l’état d’un objet de manière à ce qu’il ne soit pas directement accessible depuis l’extérieur de la classe.
-
L’héritage est une façon de structurer le code qui définit des relations hiérarchiques de factorisation entre les classes. Les attributs et les méthodes d’une classe mère deviennent automatiquement des attributs et des méthodes de ses sous-classes. Les sous-classes peuvent modifier les méthodes de la classe mère (les méthodes de la classe mère peuvent être redéfinies) et en ajouter de nouvelles pour mieux caractériser la sous-classe.
Contenu de l’article
Comme indiqué dans le cours consacré aux paradigmes de programmation, la POO a été introduite pour faciliter la création et la maintenance de programmes informatiques qui résolvent des problèmes complexes. Le principe est très simple : regrouper dans une même entité les données et les actions qui les manipulent, et limiter (voire interdire) l’accès direct aux données. Ces entités sont appelées objets en POO. Le but de ce cours est d’introduire la notion d’objet en POO et certains des concepts associés.
La programmation orientée objet (POO) est un paradigme de programmation basé sur le concept d’objets, qui peuvent contenir des données et du code : des données sous forme de champs (souvent appelés attributs ou propriétés), et du code sous forme de fonctions (souvent appelées méthodes). Dans la POO, les programmes informatiques sont conçus comme des objets qui interagissent les uns avec les autres.
1 - Objets, attributs et méthodes
Un logiciel orienté objet est constitué d’un ensemble d’objets qui interagissent entre eux pour fournir les fonctionnalités attendues.
Un objet est une abstraction (logicielle) d’une entité du monde réel qui participe à l’exécution d’un programme. Concrètement, un objet est composé de :
- un état, représenté par des variables appelées attributs ou propriétés
- un comportement, qui peut modifier l’état de l’objet et qui est représenté par des fonctions qu’on appelle méthodes.
Par exemple, pour le jeu présenté dans le cours sur les paradigmes de programmation, un objet character
pourrait être une abstraction d’un personnage du jeu, il représenterait le personnage dans le logiciel.
La figure ci-après illustre la différence fondamentale entre la structure d’un programme orienté objet et celle d’un programme procédural dans le cas du jeu où on ne tient pas compte des gains d’expérience des personnages. Dans la version procédurale, le programme est décomposé en plusieurs fonctions qui partagent des données communes (dans notre cas, les personnages et leurs données). Dans la version orientée objet, les concepts de personnage et de jeu sont implémentés en tant qu’objets.
![]() |
---|
Figure 1. Jeu mage-guerrier-voleur : version objet vs procédurale. |
Les objets interagissent entre eux en envoyant des messages, auxquels il est possible de répondre, de sorte qu’ils délèguent certaines tâches à leurs collaborateurs. On dit que les objets interagissent par appels de méthode. Un message :
- spécifie une méthode (
m
) de l’objet cible, - peut contenir des arguments (
args
), - est envoyé par un objet source (
s
) à un objet cible (t
) :t.m(args)
invoqué depuis le code de la classe décrivants
, - provoque l’exécution de
m(args)
de l’objett
, - finalement
t
renvoie le résultat dem
às
, s
peut alors continuer son exécution.
En reprenant l’exemple de la figure 1, lorsque dans le jeu jeu
il est nécessaire de savoir si le joueur mage
est vivant, le code de jeu
contiendra mage.is_alive()
. L’exécution de cette instruction implique l’envoi du message is_alive()
à l’objet mage
. L’objet de type jeu
récupère ensuite le retour de l’appel de la méthode is_alive()
, c.-à-d. un booléen.
2 - Classes, instanciation et constructeurs
Dans les langages orientés objet, un objet est créé à partir d’une classe. Le terme formel pour la création d’objets est instanciation, les objets étant également appelés instances d’une classe. Une classe est une entité qui définit la structure (état ou attributs/propriétés) et le comportement (méthodes) d’une famille d’objets. Elle se compose :
- d’un nom,
- des attributs/propriétés (leur nom et, le cas échéant, leur type),
- des méthodes (leur nom, leur type de retour, le nom et, le cas échéant, le type de chaque argument, et leur code),
- un (ou plusieurs) constructeur, une méthode particulière appelée lors de la création d’un objet et dont le rôle est d’initialiser l’état de l’objet (c’est-à-dire ses attributs/propriétés).
Une classe est une entité logicielle qui définit la structure (état ou attributs/propriétés) et le comportement (méthodes) d’une famille d’objets.
Par exemple, le code du jeu Mage-Guerrier-Voleur, peut être structuré en deux classes : Game
et Character
. Dans la figure 2, il y a trois objets qui sont des instances de la classe Character
et une instance de la classe Game
. On voit aussi que chaque objet est instance d’une seule classe, par exemple guerrier
est une instance de la classe Character
. Ça veut dire que pour créer l’objet guerrier
, le constructeur de la classe Character
a été appelé et a initialisé l’état de l’objet (donc son nom, ses points de vie de départ, etc). Et ça veut dire aussi que le type de guerrier
sera Character
.
![]() |
---|
Figure 2. Objets et classes pour le jeu mage-guerrier-voleur. |
Construire un programme orienté objet revient à identifier les classes qui le constituent, écrire ces classes (attributs/propriétés et méthodes), créer des instances (objets) et les appels de méthode entre objets.
2 - Liens entre les objets
Pour qu’un objet puisse faire un appel de méthode sur un autre objet, il doit connaître (l’adresse en mémoire de) l’objet cible. Par exemple, pour qu’un objet jeu
puisse appeler la méthode is_alive()
de l’objet guerrier
, (faire guerrier.is_alive()
), jeu
doit connaître (l’adresse de) guerrier
. C’est la notion de lien entre objets.
On peut créer (programmer) différents types de liens entre objets mais on s’intéresse ici aux associations. Ce type de liens est permanent : L’adresse de l’objet est mémorisée dans un attribut/propriété de la classe (dont l’objet est une instance). Dans notre exemple, ça veut dire qu’un des attributs de la classe Game
sera guerrier
et le type de cet attribut sera Character
. Et si on a programmé une association entre deux classes, l’association sera présente dans tous les objets de ces classes.
3 - Un peu de pseudo-code
Avant d’avancer dans les concepts, on va voir un peu de pseudo-code associé à ceux qu’on a vu jusqu’à présent. Le code ci-après définit une classe Game
avec un attribut qui est une liste de personnages (ligne 4), un constructeur (lignes 7-16) et une méthode (combat()
) (lignes 19-37). Le constructeur initialise la liste avec trois personnages, un par type.
La méthode combat()
est très similaire à la version procédurale. La différence fondamentale vient des fonctions attack()
et is_alive()
:
- pour qu’un personnage attaque un autre, la méthode
attack(Character)
de l’objetstriker
est appelée avec la cible comme paramètre (ligne 153); - pour savoir si un personnage a survecu à une attaque, on appelle la méthode
is_alive()
de l’objet cible (ligne 156).
Le pseudo-code des méthodes attack(Character)
et is_alive()
est donné plus loin dans l’article.
Ne vous attardez pas pour l’instant sur le mot clé self
dans le pseudo-code de la classe Game
. On va expliquer son rôle un peu plus tard dans l’article. Pour l’instant, considérez que lorsqu’on utilise un attribut dans le code de la classe, il faut précéder son nom par ce mot clé.
Enfin, lancer une partie correspond à créer une instance de la classe Game
et d’appeler la méthode combat()
de l’objet créé.
|
|
4 - Héritage
L’héritage est une façon de structurer le code qui définit des relations hiérarchiques entre les classes. Ce concept très simple s’inspire de notre propre façon cognitive de conceptualiser le monde. Par exemple, un ordinateur portable peut être décrit à différents niveaux de précision : une machine, un ordinateur, un notebook, un Lenovo ThinkPad. De même, dans une application orientée objet, on peut créer une classe de base (appelée classe mère) et des sous-classes plus spécifiques (ou classes enfants). Cette technique nous aide à organiser et à réutiliser le code de manière efficace.
Lors de l’utilisation de l’héritage, le code d’une sous-classe peut être :
- des attributs et des méthodes caractérisant la classe mère, car ils deviennent automatiquement des attributs et des méthodes de la sous-classe, sans qu’il soit nécessaire de les spécifier davantage, c’est-à-dire que les sous-classes héritent des attributs et des méthodes définies dans la classe mère,
- de nouveaux attributs et méthodes, ajoutés pour mieux caractériser la sous-classe,
- des méthodes de la classe mère dont le comportement a été modifié, c’est-à-dire que le comportement des méthodes de la classe mère peut être remplacé dans la sous-classe.
Par exemple, pour le jeu Mage-Guerrier-Voleur, on pourrait créer les classes Wizard
, Fighter
et Thief
qui seraient des sous-classes de Character
(voir le code ci-après). Dans ce cas, la classe Character
a 4 attributs (lignes 2-5), un constructeur et les méthodes is_alive
, gain_experience
et attack
. Les classes filles (lignes 34-52) modifient les méthodes attack()
et is_alive()
selon la classe :
-
les trois classes héritent toutes les méthodes et les attributs de la classe
Character
. Elles peuvent donc manipuler ces attributs et appeler ces méthodes, -
la classe
Wizard
(lignes 34-40) modifie le comportement de la méthodeis_alive()
. Ainsi,voleur.is_alive()
(dans la figure 3) retourne vrai si les points de vie de l’objetvoleur
sont >0 alors quemage.is_alive()
retourne vrai si les points de vie de l’objetmage
sont >= -5, -
les trois classes modifient le comportement de la méthode
gain_experience()
pour implémenter les gains d’expérience lors d’une victoire.
|
|
Maintenant on va parler du mot clé self
. Lorsque vous créez un objet, en l’occurrence un personnage, les valeurs de ses attributs lui sont propres (la valeur de l’attribut name
de l’objet mage
n’est pas la même que celle de l’objet guerrier
). C’est logique, car chaque personnage a son propre nom. Ainsi, lorsque vous appelez une méthode (ou que vous accédez à un attribut) d’un objet, il est important de savoir sur quel objet elle doit être exécutée (accédée). Dans les méthodes d’une classe, on utilise un mot clé spécifique (dans notre pseudo-code, self
, mais cela dépendra du langage de programmation utilisé) pour faire référence à l’instance courante de l’objet. Il permet ainsi d’accéder aux attributs et méthodes de l’objet depuis ses propres méthodes. Par exemple, dans la méthode attack()
(lignes 21 à 31 de la class Character
), l’appel à self.gain_experience()
signifie que la méthode gain_experience()
sera exécutée sur l’objet courant.
Si on appelle mage.attack(voleur)
(comme dans la figure 5), self
correspondra donc à mage
. Inversement, dans guerrier.attack(mage)
, self
désignera l’objet guerrier
.
![]() |
---|
Figure 3. Utilisation des classes filles de Character . |
5 - Encapsulation
L’encapsulation est le principe qui consiste à masquer ou à protéger (une partie de) l’état d’un objet afin qu’il ne soit pas accessible depuis l’extérieur de la classe. Il ne peut donc être directement accessible (et modifié) qu’à partir des méthodes de l’objet. Cette sécurité de l’état d’un objet :
- Protège les données internes de l’objet. Par exemple, elle empêche la modification d’un attribut ou garantit qu’un attribut ne soit mis à jour que lorsqu’un autre est modifié.
- Simplifie l’utilisation de l’objet. Elle ne peut être effectuée qu’en appelant les méthodes de l’objet, et non en manipulant directement ses attributs.
- Dissocier l’utilisation d’un objet de sa structure interne. Comme les attributs ne sont pas directement accessibles depuis l’extérieur de la classe, il est possible de modifier l’implémentation de l’état d’un objet sans affecter le code des autres objets.
En conséquence, l’ensemble du processus d’exécution d’un programme orienté objet repose sur un principe simple de répartition des responsabilités : chaque objet doit s’occuper de ses propres attributs.
Dans notre exemple de jeu Mage-Guerrier-Voleur, la classe Game
n’utilise pas directement les attributs de la classe Character
(voir figure 3). Par exemple, la ligne if not target.is_alive()
pourrait être remplacée par if not target.hp > 0
mais 1. il faut que le programmeur de la classe Game
sache que les vies des personnages sont codées dans un attribut hp
et 2. si l’attribut change, alors le code de la classe Game
doit changer aussi.
Pour aller plus loin
-
Introduction aux diagrammmes de classes UML.
Concepts de base des diagrammes de classes UML.
-
Polymorphisme dans la programmation orientée objet.
Introduction à un des principes fondamentaux de la POO, le polymorphisme et son impact dans la conception de programmes.