Les concepts de la POO
Modélisation
Le paradigme de la programmation orientée objet consiste à représenter les concepts métier, ainsi que les acteurs qui interagissent avec eux, sous forme de structures logicielles appelées « objets ». Les différents objets du programme contiennent des données et ont un comportement défini par leur implémentation. Ils interagissent également entre eux, s’utilisant les uns les autres afin de mener le programme à son but.
Une des étapes les plus importantes (si ce n’est la plus importante) est la modélisation du programme, qui consiste à répertorier les objets dont l’application aura besoin et à définir leurs relations. Cette étape peut comporter plusieurs niveaux d’abstraction plus ou moins hauts : de la description à grande échelle des parties de l’architecture, jusqu’aux détails d’implémentation des objets finaux qui seront développés, en passant par autant de niveaux intermédiaires qu’il est nécessaire pour la compréhension du projet.
Les parties de haut niveau ne se préoccupent pas des problèmes techniques d’implémentation : il s’agit d’organiser les concepts métier représentant la problématique que le logiciel tentera de résoudre....
Objet et classe
Un objet représente un concept métier, une brique d’abstraction du logiciel dont le but est de résoudre une problématique unique et précise tout en restant aisément et intuitivement manipulable. C’est une entité facile à visualiser mentalement, dont le rôle dans le grand échiquier de l’architecture logicielle est défini, restreint et dont les relations avec les autres objets sont logiques et harmonieuses. Si dans le design, un objet soulève la moindre hésitation, alors il faudra expliciter sa raison d’être, argumenter, réfléchir et, éventuellement, lui faire subir quelques changements afin de légitimer son utilité et sa place.
Une classe est la définition d’un objet et comprend :
-
son nom ;
-
ses attributs, c’est-à-dire les données qui définissent son état ;
-
ses méthodes, à savoir les fonctions qu’elle implémente et qui définissent son comportement.
Les attributs d’une classe représentent les propriétés du concept que la classe représente. Au sein de la classe, ses propriétés possèdent un nom et peuvent se voir attribuer une valeur, qui peut évoluer au cours du temps et selon les besoins. Cette valeur est le plus souvent typée, que ce soit avec...
Encapsulation
Un des paradigmes de la POO est que chaque classe doit avoir accès au strict minimum d’informations nécessaires pour accomplir son rôle. Une gestion trop laxiste de l’accès à l’information peut provoquer des bogues, des incompréhensions dans le code et des dépendances inutiles, voire néfastes au projet.
Il existe trois niveaux de visibilités pour les membres d’une classe, représentés par différents symboles en UML :
-
Visibilité publique (symbole +) : le membre est accessible à toutes les autres classes du programme.
-
Visibilité protégée (symbole #) : le membre est accessible uniquement aux classes dérivées de la classe en question (les classes dérivées sont présentées dans la section Héritage).
-
Visibilité privée (symbole -) : le membre est accessible uniquement à la classe qui le déclare.
L’intérêt de la visibilité est de contrôler l’accès aux membres d’une classe par le reste du programme. Par exemple, certaines données peuvent être accessibles en lecture, mais pas en écriture. Dans ce cas, il convient de ne pas fournir de méthode permettant leur modification. Ou bien il existe des attributs utiles uniquement à la classe qui les définit afin d’effectuer des calculs compliqués. La modification de ces attributs pourrait provoquer une erreur de calcul, d’où l’intérêt de les masquer. Et s’ils ne sont d’aucun intérêt pour personne d’autre que la classe qui les utilise, pourquoi implémenter une méthode d’accès qui ne servira à rien ? Encore une fois : une classe doit avoir accès uniquement à ce qui lui permet de remplir son rôle. Toute relation ou connaissance superflue représentée par du code induit de la maintenance supplémentaire...
Agrégation et composition
1. Agrégation
Une agrégation est une relation entre deux classes où l’une en « possède » une autre. En UML, cette relation est symbolisée par une ligne reliant les deux classes, où l’extrémité liée à la classe du « contenant » se termine par un losange vide. Il est également possible de préciser la cardinalité de cette relation. En UML, une cardinalité est généralement choisie parmi les valeurs suivantes :
-
0..1 : « A a une cardinalité 0..1 vers l’objet B » signifie que l’objet A peut contenir zéro ou un seul objet B.
-
1 : « A a une cardinalité de 1 vers l’objet B » signifie que l’objet A contient un et un seul objet B.
-
* ou 0..* : « A a une cardinalité de * vers l’objet B » signifie que l’objet A peut contenir zéro, un ou plusieurs objets B.
-
1..* : « A a une cardinalité de 1..* vers l’objet B » signifie que l’objet A contient obligatoirement au moins un objet B.
Certaines cardinalités peuvent être plus précises (2..5 par exemple), mais certains outils de modélisation n’autorisent que les quatre valeurs précédemment énumérées....
Interface
Une interface est un ensemble de déclarations. Par déclaration, on entend la présentation des composantes d’une méthode, typiquement son nom, le type de la valeur qu’elle retourne (optionnel), la liste de ses paramètres ainsi que leur type (optionnelle). Une déclaration se distingue de l’implémentation, qui est le corps de la méthode, où le code réside et est exécuté. Par conséquent, une interface ne se préoccupe pas des comportements et implémentations des méthodes qu’elle déclare.
L’interface est un concept extrêmement important car elle met en exergue les interactions entre les différents acteurs d’un système. Un exemple « concret » tout simple : le concept d’interrupteur est une interface entre votre doigt et le circuit électrique que vous souhaitez fermer ou ouvrir. Ce concept d’interrupteur « déclare » une « méthode » que l’on pourrait nommer presser(), « appelée » par une pression du doigt et qui va actionner un mécanisme, propre au modèle d’interrupteur installé, en charge d’ouvrir ou fermer le circuit. Il est important de noter la distinction entre le concept d’interrupteur et le modèle utilisé...
Énumération
Une énumération est un ensemble fini de valeurs distinctes.
Prenons un concept comme les jours de la semaine. Il existe plusieurs possibilités pour représenter un attribut de ce type :
-
Un entier : 0 = lundi et 6 = dimanche. Mais que vaut 32 ? -1 ? Il faut s’assurer que l’attribut se verra toujours assigner une valeur entre 0 et 6, mais ce n’est pas possible en UML qui manipule des entités métier.
-
Une chaîne de caractères. Cette option soulève le problème de l’orthographe et de la graphie. Majuscule ou pas ? Que faire si le logiciel est traduit ? Que faire si l’attribut reçoit une chaîne qui n’a rien à voir avec les jours de la semaine ?
-
Une classe privée, et donc invisible au monde, gérerait les jours de la semaine selon l’une des deux possibilités précédentes, mais offrirait des méthodes permettant de les manipuler intuitivement. Mais n’est-ce pas beaucoup d’efforts pour simplement représenter sept valeurs distinctes ?
Une énumération offre une solution simple et élégante à ce problème. Une énumération est un type (à l’instar de l’entier, du flottant, du caractère, etc.) et se manipule comme tel. Un attribut de type énumération...
Héritage
1. Héritage simple
Lorsqu’une classe hérite d’une autre, la classe « dérivée » (également appelée classe « fille ») hérite de tous les membres de la classe « de base » (ou classe « mère »). Elle peut introduire de nouveaux membres ou redéfinir ceux existant (on parle alors de « spécialisation » ou de « surcharge »).
En UML, un héritage se représente ainsi :

Un Cercle est une Forme : il hérite de tous ses attributs (centre_x, centre_y), de toutes ses méthodes (translater()) et les complète (rayon).
Pour éviter de surcharger les schémas, les attributs de la classe de base ne sont pas répétés dans la classe dérivée. Quant aux méthodes, il est courant de les indiquer uniquement si elles sont surchargées (c’est-à-dire que la classe fille implémente la méthode à sa manière) et d’omettre celles qui ne le sont pas.
L’intérêt majeur de l’héritage est de pouvoir factoriser la logique « métier » de différentes classes dans une seule : la classe de base. C’est elle qui regroupe les comportements génériques et c’est aux différentes classes dérivées d’implémenter les comportements spécifiques. Celles-ci conservent toutefois la possibilité d’invoquer les mêmes méthodes et d’accéder aux mêmes attributs que la classe mère.
Dans certains cas, il est inutile de propager aux classes filles certains membres (des attributs de calcul, des méthodes utilitaires, etc.). Il est donc possible de préciser quels sont les membres dont on désire faire hériter les classes dérivées en modifiant leur visibilité. Pour rappel : les membres publics et protégés sont automatiquement accessibles par les classes dérivées, tandis que les membres privés ne le sont pas. Encore une fois, le contrôle de l’information est essentiel en POO, et il est préférable d’éviter qu’une classe ait accès...
Diagramme UML
1. Structure vs comportement
Depuis le début de cet ouvrage, seuls les diagrammes de classes ont été présentés. En UML, il existe plusieurs types de diagrammes différents qui peuvent être divisés en deux catégories :
-
Les diagrammes de structure, qui représentent les différentes entités qui constituent le logiciel. Le diagramme de classes est un diagramme de structure. On peut également créer des diagrammes de composants, qui détaillent les grandes parties d’une architecture logicielle ou système ainsi que leurs dépendances.
-
Les diagrammes de comportement, qui se focalisent sur les interactions du programme avec le monde extérieur (interface homme-machine, évènements réseaux, signaux du système d’exploitation, etc.).
Puisqu’aucun diagramme de comportement n’a encore été vu, les sections suivantes en présenteront deux : le diagramme de cas d’utilisation et le diagramme de séquence. Dans le cadre d’une introduction à la POO, ces deux diagrammes sont les plus pertinents car ils manipulent des concepts de POO primordiaux sans être trop poussés.
2. Diagramme de cas d’utilisation
Il ne faut pas oublier que le logiciel n’est pas une fin : c’est un moyen, un outil pour effectuer des tâches...
Exercices corrigés
1. Classe simple
Énoncé : modéliser une classe qui peut recevoir une liste d’objets quelconques afin de pouvoir en récupérer ultérieurement sa longueur (le nombre d’objets dans la liste). Le schéma devra explicitement lister tous les membres nécessaires.
Corrigé :

La classe Compteur possède une méthode compter() qui prend en paramètre une liste d’objets. Elle ne retourne rien, car l’énoncé ne mentionne pas le fait de retourner le nombre d’éléments immédiatement, mais ultérieurement, ce qui sous-entend un besoin de stocker ce résultat pour une utilisation future. D’où la présence d’un attribut nombre_éléments, ni public, ni protégé, mais privé, afin d’éviter le risque qu’une classe extérieure ne puisse changer sa valeur.
Si la méthode compter() est en charge de calculer la valeur de l’attribut, la responsabilité de mettre à jour ce compteur incombe uniquement à son accesseur en écriture, si bien nommé écrire_nombre_éléments, qui est lui aussi privé. En effet, l’encapsulation s’applique également au sein même d’une classe. Le principe de responsabilité unique d’une classe doit aussi être valable pour les méthodes qui la composent, exactement pour les mêmes raisons : il est plus facile de supporter le changement si chaque « acteur » n’a qu’un seul rôle. Dans cette classe Compteur, si l’on suppose qu’il existe n méthodes modifiant directement nombre_éléments, alors changer l’attribut impliquerait de modifier l’implémentation de ces n méthodes pour refléter ce changement. Si ces n méthodes utilisaient un accesseur, alors un changement dans l’attribut qu’elles souhaitent écrire n’aurait aucun impact. La séparation des responsabilités implique moins de développement lors d’évolutions.
Pour finir, l’accesseur en lecture récupérer_nombre_éléments() retourne la valeur de l’attribut en question, et est public car c’est...