Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Chapitre 01 - Introduction à la modélisation objet

Du procédural à l'objet

On va essayer ici de cerner les limites de la programmation procédurale, qui ont donc conduit à la création du paradigme objet.

Les limites de la programmation procédurale

Commençons dans un premier temps par remonter aux origines. Les premiers programmes, écrits en langage machine, dépendaient fortement de l’architecture des ordinateurs utilisés. Ils n’étaient qu’une suite d’instructions liées au processeur qui les exécutait : mémorisation de données, opérations élémentaires arithmétiques et/ou logiques. C’étaient d’ailleurs les mêmes personnes qui concevaient, utilisaient et programmaient ces machines.

Figure 1.1. L'ENIAC (circa 1946)

L'ENIAC (circa 1946)

Crédit photo : Wikipedia.


Suite à l’invention du circuit intégré et du microprocesseur, les ordinateurs ont vu leurs capacités considérablement augmenter. Parallèlement, ils ont fait leur apparition dans la société : industrie, universités puis foyers. Leur utilisation est devenue votre quotidien.

Il est évident qu’est apparue une séparation entre les concepteurs, les programmeurs et les utilisateurs. Ces derniers ne savent généralement pas programmer, et font appel à des logiciels pour adapter leurs ordinateurs à leurs besoins. Logiciels qui devinrent naturellement de plus en plus complexes au fur et à mesure que grandirent les exigences des utilisateurs. Le codage d’algorithmes élaborés a contraint les informaticiens à concevoir des langages dits procéduraux, tels que le Fortran, Le Basic, le Pascal ou le C. Ceux ci se rapprochaient à la fois du langage et du raisonnement humain. De plus, les codes devenaient indépendants des types de processeurs et donc relativement universels.

Ces langages sont procéduraux au sens où les données sont séparées des procédures et fonctions les traitant. Pour réaliser un logiciel avec un tel langage, on commence par identifier les fonctions principales et les structures de données manipulées par ces fonctions. Ces fonctions principales pourront ensuite être découpées en fonctions auxiliaires afin de simplifier leur conception.

[Note]

Les lecteurs intéressés par la programmation procédurale consulteront le cours d'algorithmique en Python 1ADS.

Ceci dit, avec un tel paradigme de programmation deux inconvénients majeurs apparaissent :

  1. Les fonctions écrites pour un projet en particulier ne seront que rarement utilisables dans un autre projet.

  2. Le découplage entre données et fonctions fait qu’une modification des structures de données entraine de multiples points de correction du code.

Les logiciels ainsi conçus sont donc peu évolutifs et de maintenance difficile. Sachant que 40% à 80% des coûts reliés à la conception d’un logiciel résultent des mises à jours et des changements apportés une fois le logiciel sorti, on comprend l’enjeu de simplifier les opérations de maintenance.

[Note]

La donnée précédente est issue d'un article de Robert L. Glass, "Frequently Forgotten Fundamental Facts about Software Engineering".

D’où la nécessité d’une façon de penser et de programmer différente.

Une alternative : la programmation orientée objet

L'idée de base est de supprimer le découplage précédemment évoqué entre données et fonctions. Dans la programmation orientée objet, on va ainsi regrouper au sein d’une même entité certaines données et les moyens de traitement de ces données.

Une telle entité s’appellera un objet et possédera donc :

  1. Une identité.

  2. Des variables définissant son état que l'on appelera attributs.

  3. Des sous programmes gérant son comportement que l'on appelera méthodes.

Figure 1.2. Les 3 caractéristiques d'un objet : une identité, un état et un comportement

Les 3 caractéristiques d'un objet : une identité, un état et un comportement

Auteur de l'image : Grady Booch, "Object-oriented Analysis and Design".


Des objets ayant des propriétés communes, attributs et méthodes, sont alors regroupés dans une structure abstraite appelée classe. On retrouve ainsi notre manière de pensée habituelle, où nous classifions chaque élément de notre entourage : animaux, véhicules, ordinateurs, étudiants, livres...

Une classe est donc une abstraction regroupant des objets ayant les mêmes attributs et les mêmes méthodes.

Un objet est alors une instance de la classe correspondante, et se distingue des autres instances par son identité et la valeur de ses attributs.

[Note]

De façon très imagée, une classe peut donc être vue comme un moule à objet.

Example 1.1. Des premiers exemples de classes

Une classe “rectangle“ avec comme attributs la largeur et la longueur, et comme méthodes les calculs de l'aire et du périmètre :

Une classe “personne“ avec comme attributs le nom, le prénom et l'année de naissance, et comme méthode un calcul de l'âge :


[Note]

Ces dessins de classes constituent notre première rencontre avec le langage graphique UML, qui comme nous le verrons dans le cours 2UML est particulièrement bien adapté à la conception de modèles objets.

La représentation d'une classe est un rectangle possédant trois compartiments : le premier pour le nom de la classe, le second pour ses attributs (avec leur type), et le dernier pour les signatures de ses méthodes (paramètres et type de retour éventuel).

Example 1.2. Des premiers exemples d'objets

Deux instances de la classe “personne“ précédente :


[Note]

Dans ces représentations d'objets, on ne mentionne pas les méthodes car toutes les instances d'une classe possèdent les mêmes. Cela serait donc redondant. On indique juste la valeur des attributs (l'état) et le nom de l'instance (l'identité).

En POO une des questions fondamentales du développeur est donc : sur quoi porte le programme ? A opposer à la question fondamentale à se poser en programmation procédurale : à quoi sert le programme ?

Cette différence se retrouvera aussi lors de la phase de conception, où nous réserverons l’algorithmique classique à l’implémentation des méthodes, alors que pour concevoir les classes nous utiliserons donc le langage graphique UML.

[Note]

De façon savante, on dira que l’architecture du système est dictée par la structure du problème en POO et par la réponse au problème en procédural.

Les grands principes de la Programmation Orientée Objet

On va s'attarder ici sur quatre principes fondamentaux de la POO : abstraction, encapsulation, héritage et polymorphisme. Ils seront communs aux langages les plus utilisés : Java, C#, Python, C++. Voir les cours 2JVA, 2NET, et 2CPP et les trois autres chapitres de ce présent cours pour les spécificités de chaque langage.

Abstraction

L'abstraction est la démarche consistant à ne retenir que les propriétés (attributs et méthodes) pertinentes d’un objet pour un problème précis.

Figure 1.3. Une première illustration du principe d'abstraction

Une première illustration du principe d'abstraction

Auteur de l'image : Grady Booch, "Object-oriented Analysis and Design".


D’un contexte à un autre on ne retiendra donc pas nécessairement les mêmes attributs et méthodes pour modéliser une même classe.

Figure 1.4. Une seconde illustration du principe d'abstraction

Une seconde illustration du principe d'abstraction

Auteur de l'image : Grady Booch, "Object-oriented Analysis and Design".


An abstraction denotes the essential characteristics of an object that distinguish it from all other kinds of object and thus provide crisply defined conceptual boundaries, relative to the perspective of the viewer.

--Grady Booch

Example 1.3. Principe d'abstraction

  • Une classe “Personne“ aura dans un contexte SUPINFO, des attributs nom, prénom, ID, Marks,...

  • Alors que dans un contexte Sécurité Sociale, on retiendra nom, prénom, numéro d’assuré, mutuelle,...


Encapsulation

L'encapsulation est le principe interdisant l’accès direct aux attributs. On ne dialoguera avec l’objet qu’à travers une interface définissant les services accessibles à ses utilisateurs. Ce sera le rôle des méthodes.

Figure 1.5. Une illustration du principe d'encapsulation

Une illustration du principe d'encapsulation

On trouve deux intérêts majeurs à ce principe :

  1. Facilitation de l’évolution d’une application : on peut modifier les attributs d’un objet sans modifier la façon dont il est utilisé.

  2. Garantie de l’intégrité des données, car leur accès direct est interdit (ou en tout cas limité et contrôlé).

[Note]

Cette fois ci une modification des structures de données d’un objet aura un impact moindre sur l’ensemble du code. En effet, si l’interface elle ne change pas, les utilisateurs pourront continuer à se servir de l’objet de la même façon. Le programmeur aura juste à changer l’implémentation des méthodes.

Encapsulation is the process of compartmentalizing the elements of an abstraction that constitute its structure and behavior; encapsulation serves to separate the contractual interface of an abstraction and its implementation.

--Grady Booch

Lors de la déclaration d'une classe, on va pouvoir décider si l'on peut accéder aux attributs et méthodes depuis l'extérieur ou non. C'est la notion de visibilité :

  • Un attribut ou une méthode sont dits privés si leur utilisation est interdite en dehors de la classe.

  • Un attribut ou une méthode sont dits publics si leur utilisation est autorisée en dehors de la classe.

Le principe d'encapsulation est donc de déclarer les attributs de façon privée et les méthodes de façon publique.

Une question naturelle se pose alors : comment accéder aux attributs si ceux-ci sont déclarés de façon privée ? La réponse est : par l’intermédiaire de méthodes bien particulières, les getter et les setter :

  • getter : il s’agit de méthodes publiques dont le rôle est de retourner la valeur d’un attribut.

  • setter : il s’agit de méthodes publiques dont le rôle est de fixer la valeur d’un attribut. Cela est fait la plupart du temps avec un contrôle de la nouvelle valeur.

[Note]

Grace aux setter on garantit l’intégrité des données : elles ne peuvent être modifiées n’importe comment.

Example 1.4. Une classe "compte" avec des attributs privés

Pour changer la valeur du solde on doit nécessaire utiliser la méthode "setSolde" qui contrôlera la nouvelle valeur afin par exemple de ne pas être à découvert :

On crée ainsi en général un getter et un setter par attribut.


[Note]

Bien souvent nous nommerons les getter et setter d'un attribut X par getX et setX, comme cela est fait dans l'exemple précédent.

Dans la représentation de la classe le + signifie public et le - privé.

[Note]

L'implémentation de ce principe en Python sera étudiée dans le second chapitre de ce cours.

Héritage

L'héritage est une relation de spécialisation/généralisation entre deux classes. Elle indique qu’une classe dite classe fille spécialise une autre classe dite classe mère, i.e. qu’elle possède les attributs et les méthodes de la classe mère plus d’autres qui lui sont propres. On parle aussi de super classe et de sous classe.

Example 1.5. Une relation d'héritage entre les classes "personne" et "étudiant Supinfo"

Un étudiant SUPINFO est bien une spécialisation d'une personne :


[Note]

On continue la petite introduction en douceur aux diagrammes UML… Cette flêche symbolise donc la relation d'héritage.

Dans la sous classe on ne mentionne que les nouveaux attributs et les nouvelles méthodes, qui viennent s’additionner à ceux et celles de la super classe.

On trouve deux intérêts majeurs à ce principe :

  1. On construit une hiérarchie de classes. On évite ainsi des répétitions dans le code, en encourageant la réutilisation de classes déjà existantes.

  2. Cela permet de simplifier la conception de la modélisation.

On pourra envisager ce concept de deux façons différentes.

Tout d'abord, on pourra en avoir une vision ascendante, en procédant par généralisation. Si on décèle des attributs et des méthodes communs à des classes différentes, l’héritage permet alors de les factoriser afin de faciliter la conception et la maintenance du code.

Example 1.6. La vision ascendante de l'héritage

On considère les classes "étudiant en informatique" et "étudiant en commerce" :

On décèle un attribut et une méthode en commun dans ces deux classes, on les factorise donc dans une classe "étudiant" :


Mais on peut également en avoir une vision descendante de l'héritage, en procédant par spécialisation. On peut ainsi créer des classes spécialisées à partir d'une classe de base. Le niveau de spécialisation dépend alors du niveau d’abstraction que l’on souhaite. On procède souvent ainsi quand on veut réutiliser des classes déjà existantes.

Example 1.7. La vision descendante de l'héritage

A partir de la classe "animal" on se spécialise de plus en plus jusqu'à la classe "chat" :


[Note]

Le lecteur attentif doit déjà avoir deviné que cette relation d'héritage nécessitera un troisième type de visibilité pour les attributs d'une classe, afin que ceux-ci soient manipulables par les classes filles mais pas par l'extérieur de la descendance. On reviendra sur ce sujet dans la suite du cours.

Pour conclure ce paragraphe mentionnons une possibilité offerte par certains langages orientés objets, le fait qu'une classe puisse posséder plusieurs classes mères. On parle alors d'héritage multiple.

Example 1.8. Une relation d'héritage multiple

Un carré est à la fois un rectangle et un losange :


Des difficultés peuvent cependant apparaître quand les classes mères possèdent des méthodes de mêmes noms qui ne sont pas redéfinies au sein de la classe fille.

Example 1.9. Une ambiguïté dans une relation d'héritage multiple

Les classes “rectangle" et "losange" possèdent toutes les deux une méthode "dessiner" mais pas la classe "carré" :

Si on appelle la méthode "dessiner" sur un objet de la classe "carré" on aura une ambiguïté : quelle méthode de la classe mère utiliser ? On verra comment résoudre ce problème dans la suite du cours.


[Note]

D’un langage à un autre, la gestion de l’héritage multiple sera complètement différente.

Le Java et le C# quand à eux interdisent même ce principe.

L'étude de l'héritage (simple et multiple) en Python fera l'objet du troisième chapitre de ce cours.

Polymorphisme

Le polymorphisme est littéralement la faculté de prendre plusieurs formes. En POO, c’est un mécanisme qui permet à une sous classe de redéfinir une méthode dont elle a hérité tout en gardant la même signature. Selon le contexte, le programme optera pour la "bonne" méthode".

Example 1.10. Une illustration du polymorphisme

La méthode "jouer" est présente dans la classe mère "musicien" et dans ses classes filles "guitariste", "pianiste" et "bassiste" :

L’appel de la méthode "jouer" sur tous les objets héritant de la classe "musicien" produira alors un résultat différent selon la sous classe à laquelle ils appartiennent.


On décèle un intérêt majeur : le code gagne en généricité. On peut ainsi appeler des méthodes portant les mêmes noms mais produisant des effets différents selon le type réel de l’objet.

Example 1.11. De la généricité due au polymorphisme

On considère la relation d'héritage suivante :

Imaginons que l’on possède une liste appelée "mesFigures" constituée d’une centaine d’objets dérivant de la classe "figurePlane", et que l'on souhaite dessiner ces objets un par un. La puissance du polymorphisme permettra d’utiliser une routine de la forme :

for i in range(100):
    mesFigures[i].dessiner()

A chaque itération, le programme choisira la méthode "dessiner" correspondant au type d’objet en question, que ce soit un rectangle, un triangle, un losange ou un cercle. Et ce sans lui spécifier directement la nature de l’objet.


[Note]

On verra dans le dernier chapitre de ce cours comment s'implémente le polymorphisme en Python.

A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels