On se réfère aux codes suivants pour comprendre les différences de fonctionnement entre un système orienté procédure en C et un système orienté objet en C++.
On note que :
Les objets sont les concepts dominants des systèmes orientés objet.
Ce sont des abstractions qui encapsulent les propriétés et le comportement des entités d'un système.
Chaque objet possède une identité qui le distingue des autres objets du système.
Souvent, les aspects distinctifs d'un objet sont évidents.
Par exemple, une différence de couleur, d'emplacement sur un écran, de taille ou de contenu permet de distinguer une fenêtre d'une autre sur le bureau d'un ordinateur.
Mais les apparences peuvent être trompeuses, et même deux objets partageant les mêmes caractéristiques peuvent avoir des identités différentes.
Deux automobiles peuvent avoir le même fabricant, le même modèle, les mêmes options et les mêmes couleurs, mais restent deux voitures différentes.
Dans le monde réel, les deux voitures sont distinguées par leur numéro d'identification.
De même, les systèmes de programmation qui gèrent plusieurs entités ont besoin d'un mécanisme d'identité.
Un pointeur vers la mémoire allouée ou un nom de variable dans une table de symboles gérée par le système sont souvent utilisés pour distinguer les objets d'un système.
Dans un système de base de données, un ensemble de clés d'identification (appelé n-uplet) identifie une entité.
Mais en quoi les systèmes orientés objet diffèrent-ils des systèmes de programmation procédurale conventionnels ?
La principale différence réside dans la manière dont les deux approches traitent l'abstraction des données.
Les systèmes conventionnels limitent l'abstraction au typage des données, tandis que les systèmes orientés objet créent des abstractions pour les données et les opérations qui peuvent leur être appliquées.
En fait, un système orienté objet regroupe les données et les opérations dans une seule structure de programmation appelée objet.
Ensemble, ces données et opérations constituent les propriétés d'un objet.
Lorsqu'une opération est appliquée à un objet, le mécanisme de liaison dynamique du langage de programmation exécute la procédure appropriée pour cet objet.
Ce n'est pas le cas dans les systèmes orientés procédure.
Le programmeur doit fournir une logique pour décider quelle procédure appeler.
Les systèmes qui gèrent plusieurs types sont souvent encombrés d'instructions de cas pour sélectionner la procédure appropriée pour une opération.
À mesure que de nouveaux types sont ajoutés à ces systèmes, le code qui distribue les opérations en fonction du type de données doit être étendu pour gérer le nouveau type.
On note que :
Par exemple, dans un programme visant à afficher différents types de primitives, le pseudo-code suivant illustre les différences entre un système orienté procédure et un système orienté objet.
Un système orienté procédure sépare la structure de données et les fonctionnalités qui agissent sur ces données.
Extrait 2.1 : Cas du système orienté procédure en C
...
void DrawPrimitive(aPrim)
{
if (aPrim->type == TRIANGLE)
DrawTriangle(aPrim);
else if (aPrim->type == SQUARE)
DrawSquare(aPrim);
else if (aPrim->type == CIRCLE)
DrawCircle(aPrim);
...
}
...
Extrait 2.2 : Cas du système orienté objet en C++
...
Primitive *aPrim;
...
aPrim ->Draw();
...
On se réfère à la figure 2.1 pour comprendre le mappage d'un objet réel vers une abstraction d'objet.
On note que :
Un objet est une abstraction qui modélise l'état et le comportement des entités d'un système.
L'abstraction est un processus mental qui extrait les aspects essentiels d'une situation dans un but précis.
Les entités sont des éléments du système qui ont une identité.
Les chaises, les avions et les caméras sont des objets qui correspondent à des entités physiques du monde réel.
Les arbres binaires, les tables de symboles et les collections ordonnées sont des objets qui n'existent que dans le monde de l'informatique.
La figure 2.1 illustre l'abstraction qui se produit lorsque l'on associe l'état et le comportement d'un composant système à un objet.
On note que :
Ici, l'objet est un type d'arbre particulier : un chêne des marais (PinOak).
Dans cette application, nous souhaitons simuler la croissance de différents types d'arbres au cours d'une saison.
Pour notre application, nous avons décidé que les variables d'état importantes sont l'âge (Age), le diamètre du tronc (TrunkDiameter), la hauteur (Height) et l'apparence (Habit) de l'arbre.
Pour capturer le comportement du chêne des marais, nous disposons de méthodes permettant de simuler la croissance (Grow) et les effets saisonniers (SetSeason) correspondant au printemps, à l'été, à l'automne et à l'hiver.
Il existe également des méthodes (non présentées) permettant de définir et d'obtenir les variables d'état actuelles.
On note que :
Lorsqu'une opération portant le même nom est appliquée à des objets de classes différentes, on dit que l'opération est polymorphe.
Par exemple, notre système de visualisation VTK possède une opération nommée Render() qui peut être appliquée à de nombreux objets différents.
L'implémentation d'une opération pour une classe particulière est appelée une méthode.
L'opération d'impression d'un objet (vtkMatrix4x4) est implémentée dans sa méthode d'impression.
Autrement dit, il existe du code qui sait comment imprimer les objets de la classe (vtkMatrix4x4) et non ceux des autres classes.
Les objets savent quelle méthode utiliser car elles sont conservées dans la structure de données de chaque objet.
Dans la plupart des systèmes, le code des méthodes est partagé par tous les objets d'une même classe.
Figure 2.1 : Mappage d'un objet réel vers une abstraction d'objet. Les objets réels sont différents types d'arbres. L'un de ces objets (un chêne des marais, a pin oak) est mappé vers l'objet informatique appelé PinOak.
On se réfère au code suivant pour comprendre la surcharge de méthode en C++.
On note que :
Certains langages de programmation, dont C++, définissent une méthode en combinant le nom d'une opération avec ses types d'arguments.
Ce processus, appelé surcharge d'opération, est une technique puissante qui permet d'utiliser le même nom pour des opérations logiquement similaires.
Par exemple, la définition de classe ci-dessous définit trois méthodes pour calculer le carré d'un nombre.
Même si ces méthodes ont le même nom d'opération, elles sont uniques car C++ utilise à la fois le nom de l'opération et les types d'arguments des opérations.
Extrait 2.3 : Classe math
...
class math
{
float square(float x);
int square(int x);
double square(double x);
};
...
On se réfère au code suivant pour comprendre la création d'une instance de classe en C++.
On note que :
Pour utiliser un membre d'une classe à des fins spécifiques, nous créons une instance de cette classe (processus d'instanciation).
La création d'une instance établit l'identité de l'instance, notamment en spécifiant son état initial.
La classe de l'instance sert de modèle à la création, définissant les noms de chacun de ses attributs et opérations.
La création établit les similitudes et les différences entre cette instance et les autres instances de la même classe.
Les similitudes sont les noms et les types de ses attributs, ainsi que les méthodes qui implémentent ses opérations.
Les différences sont les valeurs spécifiques des attributs.
La création d'une instance de classe varie d'un langage de programmation à l'autre.
En C++, un programme crée une instance en utilisant une forme déclarative telle que (MyAngleClass angle) ; qui crée un objet à partir de la pile du programme, ou en appliquant l'opération (new), qui crée l'objet à partir du tas du programme, comme illustré ci-dessous.
On note que :
Les objets VTK doivent être créés à partir du tas du programme.
Il est recommandé d'utiliser (vtkNew) ou (vtkSmartPointer) lors de la création d'objets VTK, car ils conservent un décompte des références.
Ainsi, lorsqu'ils sortent de la portée, ils sont automatiquement supprimés.
Vous n'avez donc pas besoin de penser à utiliser (Delete) pour détruire les objets.
...
// creates an object from the program stack
MyAngleClass angle;
// creates the object from the program heap
MyAngleClass *angle = new MyAngleClass();
...
delete ange;
// For VTK we do it using either of these two approaches.
vtkNew<vtkActor> aBall;
// or
vtkSmartPointer<vtkActor> aBall = vtkSmartPointer<vtkActor>::New();
...
On se réfère au la figure 2.2 pour comprendre l'héritage de classe en C++ à travers la hiérarchie héréditaire du chêne des marais (PinOak).
On note que :
L'héritage est un mécanisme de programmation qui simplifie l'ajout de nouvelles classes à un système lorsqu'elles diffèrent légèrement des classes existantes.
La notion d'héritage découle du constat que la plupart des systèmes peuvent être spécifiés à l'aide d'un système de classification hiérarchique.
Les embranchements de la vie sur Terre en sont un bon exemple.
Nous avons créé précédemment un objet correspondant à un chêne des marais (PinOak).
Les propriétés de cet arbre peuvent être décrites plus en détail grâce à l'héritage à la figure 2.2.
La classification présentée ici est basée sur le système des cinq règnes de (Margulis et Schwartz).
Dans ce système, le biote (Biota) est classé comme appartenant à l'un des cinq règnes suivants : Prokaryotae (bactéries), Protoctista (algues, protozoaires et myxomycètes), Fungi (champignons, moisissures, lichens), Plantae (mousses, fougères, plantes à cônes et à fleurs) et Animalia (animaux avec et sans colonne vertébrale).
Sous ce niveau se trouvent les classifications : division, classe, ordre, famille, genre et espèce.
La figure 2.2 montre le règne, la division, la classe, le genre et l'espèce du chêne des marais (PinOak).
On note que :
L'organisation des objets selon une hiérarchie d'héritage offre de nombreux avantages.
Les propriétés d'une classification générale sont également celles de sa sous-classification.
Par exemple, nous savons que toutes les espèces du genre (Quercus) forment des glands (acorns).
D'un point de vue logiciel, cela signifie que toutes les variables d'instance et méthodes d'une superclasse sont automatiquement héritées par sa sous-classe.
Cela permet d'apporter des modifications à plusieurs objets simultanément en modifiant leur superclasse.
De plus, si nous souhaitons ajouter une nouvelle classe (par exemple, un chêne rouge, RedOak) à la hiérarchie, nous pouvons le faire sans dupliquer les fonctionnalités existantes.
Il suffit de différencier la nouvelle classe des autres en ajoutant de nouvelles variables d'instance ou en surchargeant les méthodes existantes.
La possibilité d'ajouter rapidement de nouvelles classes légèrement différentes des classes existantes favorise l'extensibilité d'un système.
L'héritage peut être dérivé de haut en bas grâce à un processus appelé spécialisation, ou créé de bas en haut en combinant des classes similaires lors d'un processus appelé généralisation.
L'héritage implique une hiérarchie de classes, une ou plusieurs classes étant les superclasses d'une ou plusieurs sous-classes.
Une sous-classe hérite des opérations et des attributs de ses superclasses.
En C++, les sous-classes sont appelées classes dérivées et les superclasses, classes de base.
Une sous-classe peut ajouter des opérations et des attributs supplémentaires qui modifient les propriétés héritées de ses superclasses.
Grâce à cet héritage, un objet peut reproduire le comportement de sa superclasse, ainsi que tout comportement supplémentaire souhaité.
Il peut également restreindre, ou surcharger, les opérations implémentées par sa superclasse.
Figure 2.2 : Hiérarchie d'héritage pour le chêne des marais (pin oak).
On se réfère au code suivant pour comprendre les concepts d'interface, d'abstraction et de délégation en C++.
On note que :
Les classes qui n'existent que pour servir de superclasses à leurs sous-classes sont appelées classes abstraites.
La création d'instances d'une classe abstraite est généralement interdite.
Les classes abstraites sont utiles pour rassembler les attributs et les méthodes que toutes les sous-classes utiliseront.
Elles peuvent également définir des protocoles de comportement pour leurs sous-classes. - Il s'agit d'une utilisation puissante de l'héritage qui se reflétera dans la conception de notre système de visualisation.
Les classes abstraites peuvent appliquer des séquences complexes, contrôler des protocoles et garantir un comportement uniforme.
Elles déchargent les sous-classes individuelles de la responsabilité des protocoles complexes et isolent le protocole dans la superclasse.
Un exemple de package de traçage simple illustre la puissance des classes abstraites.
Prenons une application de présentation de données permettant divers tracés bidimensionnels.
Cette application doit prendre en charge les graphiques en courbes et les graphiques à barres horizontales et verticales.
Le processus de conception identifie les propriétés communes à tous les tracés, notamment le titre, les axes et la légende.
Nous créons ensuite une classe abstraite appelée TwoDPlot pour contenir ces attributs communs.
Le comportement commun peut également être capturé dans TwoDPlot via sa méthode plot.
On note que :
Une classe abstraite peut ou non fournir un comportement par défaut pour chaque opération.
Dans cet exemple, le comportement par défaut pour le dessin des bordures et des titres peut être fourni.
Les sous-classes de TwoDPlot définiront ensuite leurs propres fonctions pour les autres méthodes.
La spécification du protocole précise explicitement les méthodes auxquelles une sous-classe de TwoDPlot doit répondre.
Dans l'exemple ci-dessus, les sous-classes devront définir leurs propres méthodes pour dessiner les axes, les données et la légende.
Certaines sous-classes peuvent utiliser les méthodes de TwoDPlot pour dessiner les bordures, tandis que d'autres peuvent nécessiter leur propre version de cette méthode.
L'interface abstraite définie dans TwoDPlot facilite l'ajout de nouvelles classes de tracés 2D et les sous-classes résultantes tendent à être plus uniformes et cohérentes.
On note que :
Un autre mécanisme, la délégation, est utile pour isoler et réutiliser un comportement.
Grâce à la délégation, un objet applique des opérations à l'un de ses attributs, qui est un objet.
Par exemple, dans la boîte à outils de visualisation, l'objet (vtkTransform) délègue son opération (Identity) à son attribut (vtkMatrix4x4).
Cette instance de (vtkMatrix4x4) effectue ensuite l'opération.
Il existe de nombreux autres concepts utiles orientés objet, mais pour l'instant, nous disposons de suffisamment d'informations pour décrire comment utiliser les objets pour concevoir un système.
Extrait 2.4 : TwoDPlot
...
Method Plot
{
Draw the border;
Scale the data;
Draw the axes;
Draw the data;
Draw the title;
Draw the legend;
}
...
On se réfère aux exemples suivants pour comprendre la technique de modélisation orientée objet.
On note que :
La conception de tout système logiciel d'envergure est une tâche colossale, et les premières étapes de sa conception sont souvent les plus difficiles.
Quelle que soit la technique de conception choisie, une compréhension approfondie du domaine d'application du système est essentielle.
Concevoir un système de commandes de vol électriques sans une connaissance approfondie des systèmes de contrôle matériels sous-jacents est difficile.
Bien entendu, tous les logiciels de vol ne sont pas conçus par des ingénieurs aéronautiques ; une spécification système est donc indispensable.
La profondeur des informations contenues dans ces spécifications varie d'une application à l'autre.
La conception d'un système orienté objet commence par une étape de modélisation qui extrait les objets et leurs relations avec d'autres objets à partir d'un énoncé de problème ou d'une spécification d'exigences logicielles.
Le concepteur doit d'abord comprendre parfaitement le problème à résoudre.
Cela nécessite souvent une connaissance approfondie du domaine du problème ou l'accès à des spécifications détaillées du problème à résoudre.
Ensuite, les abstractions majeures doivent être identifiées au sein du système.
À ce niveau élevé de conception, les abstractions constitueront le premier ensemble d'objets.
Par exemple, un système de suivi d'un portefeuille d'investissement nécessitera des objets tels que des actions, des obligations et des fonds communs de placement.
Un système d'animation par ordinateur pourrait nécessiter des acteurs, des caméras et des éclairages.
Un système de tomodensitométrie médicale sera équipé d'une table, d'une source de rayons X, de détecteurs et d'un portique.
Notre système de visualisation comportera des modèles, des iso-surfaces, des lignes de courant et des plans de coupe.
Lors de cette étape de modélisation, nous recherchons des objets, des propriétés et des relations dans le domaine du problème.
Par la suite, lors de plusieurs passages de conception, le modèle sera étendu.
La modélisation est une étape de la plupart des processus de conception, qu'il s'agisse d'un navire, d'une maison, d'un système électronique ou d'un logiciel.
Chaque discipline suit une méthodologie qui utilise des techniques spécifiquement créées pour rendre le processus de conception efficace et rentable.
Ces techniques sont appelées « outils du métier ».
Un ingénieur électricien utilise des schémas et des diagrammes logiques, un architecte des dessins et des maquettes, et un constructeur naval des maquettes.
De même, les concepteurs de logiciels ont besoin d'outils permettant de modéliser le système.
Ces outils doivent être suffisamment expressifs pour aider le concepteur à évaluer une conception par rapport à un cahier des charges et à la communiquer aux autres membres de l'équipe.
Nous utilisons la technique de modélisation objet (OMT) développée chez GE par Jim Rumbaugh et ses collègues.
L'OMT utilise trois modèles pour spécifier une conception orientée objet : un modèle objet, un modèle dynamique et un modèle fonctionnel.
Chaque modèle décrit un aspect différent du système et possède une technique de diagramme correspondante qui nous aide à analyser, concevoir et implémenter des systèmes logiciels.
...
- Un système de suivi d'un portefeuille d'investissement :
Nécessitera des objets tels que des actions, des obligations et des
fonds communs de placement.
- Un système d'animation par ordinateur :
Pourrait nécessiter des acteurs, des caméras et des éclairages.
- Un système de tomodensitométrie médicale :
Sera équipé d'une table, d'une source de rayons X, de détecteurs et
d'un portique.
- Notre système de visualisation :
Comportera des modèles, des isosurfaces, des lignes de courant et des
plans de coupe.
...
On se réfère à la figure 2.3 pour comprendre le concept de modèle objet.
On note que :
Le modèle objet identifie chaque objet du système, ses propriétés et ses relations avec les autres objets du système.
Pour la plupart des systèmes logiciels, le modèle objet domine la conception.
La technique graphique OMT utilise des rectangles pour représenter les classes d'objets et divers connecteurs pour illustrer l'héritage et les autres relations entre objets.
Les classes d'objets sont représentées par des rectangles pleins.
Les instances sont représentées par des rectangles pointillés.
Le nom de la classe ou de l'instance occupe le haut du rectangle.
Une ligne sépare le nom de la classe de la section suivante contenant les attributs ; une troisième section décrit les méthodes.
Les relations entre les objets sont représentées par des segments de ligne reliant les deux objets associés.
En OMT, les relations sont appelées associations et peuvent avoir différentes cardinalités : un à un, un à plusieurs et plusieurs à plusieurs.
Les associations spécifiques qui représentent des conteneurs d'autres objets sont appelées agrégations.
Les associations peuvent être étiquetées avec des rôles. (Les rôles sont des noms donnés aux associations et servent à décrire plus précisément la nature de l'association.)
L'OMT représente l'héritage par un triangle, la superclasse étant attachée au sommet et les sous-classes à la base du triangle.
La figure 2.3 illustre un modèle objet pour les dispositifs de localisation dans un système de réalité virtuelle.
Le premier objet de la hiérarchie des classes est locator.
Cette classe abstraite spécifie les attributs et méthodes communs à tous les localisateurs.
Les sous-classes de locator sont locator2D et locator3D.
Dans la version actuelle de ce modèle objet, le localisateur ne possède qu'un seul attribut, un dispositif (device), et deux méthodes : open() et close().
Les deux sous-classes de locator, locator2D et locator3D, sont également des classes abstraites, contenant des attributs et des méthodes qui les distinguent par leur dimensionnalité spatiale.
Par exemple, locator3D a une position x, y et z,
Tandis que locator2D a une position x, y.
Les deux localisateurs possèdent une méthode locate() qui met à jour la position actuelle.
Dans la classe locator3D, locate() met également à jour l'orientation.
Les sous-classes de locator3D incluent du matériel de trois fabricants différents : flock, pixsys et logitek, ainsi qu'une classe abstraite de positionneur articulé (articulated).
Les trois classes d'objets pour le matériel contiennent des méthodes spécifiques à chaque appareil.
Chaque méthode sait convertir les codes matériels renvoyés par l'appareil.
Elles savent que pour être considérées comme une sous-classe de locator3D, elles doivent implémenter une opération de position et d'orientation qui fournira les coordonnées x, y, z et trois rotations angulaires pouvant être composées dans une matrice de transformation.
Le modèle d'objet montre également que le localisateur articulé (articulated) possède des angles et des liaisons.
Deux localisateurs articulés spécifiques sont immersion (immersion) et fantôme (phantom).
Un modèle d'objet ainsi schématisé sert de point de départ à la conception et à la discussion.
Il révèle les méthodes et attributs communs, ainsi que les caractéristiques distinctives de chaque classe.
Plus tard, lors de l'implémentation, nous convertirons ces modèles d'objets en objets logiciels.
Le langage informatique choisi pour l'implémentation déterminera les détails de la conversion.
Figure 2.3 : Modèle d'objet pour les dispositifs de localisation.
On se réfère à la figure 2.4 pour comprendre le concept de modèle dynamique.
On note que :
Le modèle objet décrit la partie statique d'un système, tandis que le modèle dynamique détaille les séquences d'événements et les dépendances temporelles du système.
L'OMT utilise des diagrammes d'état pour modéliser la dynamique du système.
Les modèles dynamiques sont fréquemment utilisés pour concevoir des systèmes de contrôle et des interfaces utilisateur.
Notre système de visualisation présente des aspects de séquence et de contrôle limités ; nous ne nous attarderons donc pas sur les diagrammes d'état.
Cependant, si nous devions concevoir une interface conviviale pour une montre-bracelet numérique, le diagramme d'état de la figure 2.4 serait utile.
Les ovales du diagramme représentent un état ; les flèches indiquent une transition d'un état à un autre ; et les libellés des flèches indiquent un événement provoquant la transition.
Cet exemple présente trois états d'affichage et plusieurs états de réglage.
L'événement b1 signifie que le bouton 1 est enfoncé.
Cette montre possède trois boutons.
Le diagramme montre ce qui se passe dans chaque état lorsque l'un des trois boutons est enfoncé.
Le diagramme montre clairement que b1 permet de passer d'un mode d'affichage à l'autre pour l'heure, la date et l'alarme.
b2 permet de passer du mode affichage au mode réglage ou de sélectionner le champ à modifier dans un mode donné.
b3 incrémente le champ sélectionné d'une unité.
Le diagramme d'état montre également ce qui se passe lorsque des boutons non autorisés sont enfoncés.
Si la montre affiche l'heure et que le bouton b3 est enfoncé, rien ne se passe.
Si le bouton b3 est enfoncé alors que la montre affiche l'alarme, celle-ci est activée/désactivée.
Figure 2.4 : Diagramme d'état d'une montre-bracelet.
On se réfère à la figure 2.5 pour comprendre le concept de modèle fonctionnel.
On note que :
Le modèle fonctionnel montre comment les données circulent dans le système et comment les processus et les algorithmes les transforment.
Il met également en évidence les dépendances fonctionnelles entre les processus.
La mise en évidence de ces relations influence les associations dans le modèle objet.
Les principaux composants d'un diagramme de flux de données (DFD) sont les sources de données, les récepteurs de données et les processus.
Les sources et les récepteurs de données sont représentés par des rectangles.
Les ellipses représentent les processus.
Les magasins de données sont représentés par deux lignes horizontales.
Les DFD sont utiles pour décrire le flux global du système. Ils peuvent également être utilisés pour décrire tout processus qui transforme une représentation de données en une autre. Les processus identifiés dans le DFD lors de la modélisation fonctionnelle peuvent apparaître sous forme d'opérations ou d'objets dans le modèle objet.
La figure 2.5 présente un diagramme de flux de données pour un système d'imagerie médicale 3D.
Le diagramme illustre l'acquisition de données d'un scanner CT(Computed Tomography) ou IRM.
La série de coupes transversales (Slices) fournies par le scanner est d'abord traitée par des filtres de traitement d'image (Filter) afin d'améliorer les caractéristiques des coupes en niveaux de gris (Slices).
Un processus de segmentation (Segment) identifie les tissus et produit des étiquettes pour les différents tissus présents dans les coupes (Labeled Slices).
Ces coupes étiquetées (Labeled Slices) sont ensuite soumises à un processus d'extraction de surface (Surface Extraction) pour créer des triangles (Triangles) à la surface de chaque tissu.
Le processus de rendu (Render) transforme la géométrie en image (Image).
Le processus d'écriture (Write) stocke également les triangles (Triangles) dans un fichier (Triangle File).
Les triangles (Triangles) peuvent ensuite être lus (Render) et restitués en image (Image).
La décision de transformer les processus en objets ou en opérations est reportée à plus tard.
Dans la suite de ce tutoriel, la section (Visualisation sur le Web) utilise des diagrammes de flux de données pour modéliser le pipeline de visualisation.
Figure 2.5 : Diagramme de flux de données.