Section courante

A propos

Section administrative du site

Programmation orientée objet (POO)

La programmation orientée objet (POO) est une méthode de programmation imitant étroitement la façon dont nous faisons tous les choses. Il s'agit d'une évolution naturelle des innovations antérieures à la conception du langage de programmation : il est plus structuré que les tentatives précédentes de programmation structurée; et il est plus modulaire et abstrait que les tentatives précédentes d'abstraction de données et de masquage de détails. Trois propriétés principales caractérisent un langage de programmation orienté objet :

Les extensions de langage de Turbo Pascal vous offrent toute la puissance de la programmation orientée objet : plus de structure et de modularité, plus d'abstraction et de réutilisabilité intégrées directement dans le langage. Toutes ces fonctionnalités s'ajoutent à un code plus structuré, extensible et facile à entretenir.

Le défi de la programmation orientée objet est qu'elle vous oblige à mettre de côté des habitudes et des façons de penser à la programmation étant standard dans les années 1980. Une fois que vous avez fait cela, cependant, la POO est un outil simple, direct et supérieur pour résoudre de nombreux problèmes affligeant les programmes traditionnels.

Une note pour vous ayant fait de la programmation orientée objet dans d'autres langages : mettez de côté vos impressions précédentes sur la POO et apprenez les fonctionnalités orientées objet de Turbo Pascal selon leurs propres termes. La POO n'est pas une manière unique de programmer; c'est un continuum d'idées. Dans sa philosophie objet, Turbo Pascal ressemble plus à C++ qu'à Smalltalk. Smalltalk est un interpréteur, alors que depuis le début, Turbo Pascal est un pur compilateur de code natif. Les compilateurs de code natif font les choses différemment (et beaucoup plus rapidement) que les interpréteurs.

Et une note pour vous n'ayant aucune idée de ce qu'est la POO : c'est aussi bien. Trop de battage médiatique, trop de confusion et trop de gens parlant de quelque chose qu'ils ne comprennent pas ont considérablement brouillé les cartes ces dernières années. Efforcez-vous d'oublier ce que les gens vous ont dit sur la POO. La meilleure façon (en fait, la seule façon) d'apprendre quoi que ce soit d'utile sur la POO est de faire ce que vous êtes sur le point de faire : asseyez-vous et essayez-le vous-même.

Objets ?

Oui, des objets. Regardez autour de vous... il y en a une : la pomme que vous avez apportée pour le déjeuner. Supposons que vous alliez décrire une pomme en termes de logiciel. La première chose que vous pourriez être tenté de faire est de la séparer : Soit S la surface de la peau ; Soit J le volume fluide de jus qu'il contient ; Soit F le poids du fruit à l'intérieur ; Soit D le nombre de graines ....

Ne pensez pas de cette façon. Pensez comme un peintre. Vous voyez une pomme et vous peignez une pomme. L'image d'une pomme n'est pas une pomme ; c'est juste un symbole sur une surface plane. Mais il n'a pas été résumé en sept nombres, tous isolés et indépendants dans un segment de données quelque part. Ses composantes restent ensemble, dans leurs relations essentielles les uns avec les autres.

Les objets modélisent les caractéristiques et le comportement des éléments du monde dans lequel nous vivons. Ils constituent jusqu'à présent l'abstraction de données ultime.

Les objets conservent toutes leurs caractéristiques et leur comportement ensemble

Une pomme peut être séparée, mais une fois qu'elle a été séparée, ce n'est plus une pomme. Les relations des parties avec le tout et les unes avec les autres sont plus claires lorsque tout est conservé dans une seule enveloppe. C'est ce qu'on appelle l'encapsulation, et c'est très important. Nous reviendrons sur l'encapsulation dans quelques instants.

Tout aussi important, les objets peuvent hériter des caractéristiques et du comportement de ce que l'on appelle les objets ancêtres. C'est un saut intuitif; L'héritage est peut-être la plus grande différence entre la programmation Turbo Pascal orientée objet et la programmation Pascal Standard aujourd'hui.

Héritage

Le but de la science est de décrire le fonctionnement de l'univers. Une grande partie du travail de la science, dans la poursuite de cet objectif, consiste simplement à créer des arbres généalogiques. Lorsque les entomologistes reviennent d'Amazonie avec un insecte auparavant inconnu dans un bocal, leur préoccupation fondamentale est de déterminer où cet insecte s'inscrit dans le tableau géant sur lequel les noms scientifiques de tous les autres insectes sont rassemblés. Il existe des cartes similaires de plantes, de poissons, de mammifères, de reptiles, d'éléments chimiques, de particules subatomiques et de galaxies externes. Ils ressemblent tous à des arbres généalogiques : une seule catégorie globale au sommet, avec un nombre croissant de catégories sous cette seule catégorie, se déployant jusqu'aux limites de la diversité.

Dans la catégorie des insectes, par exemple, il y a deux divisions : les insectes aux ailes visibles et les insectes aux ailes cachées ou sans ailes du tout. Sous les insectes ailés se trouve un plus grand nombre de catégories : mites, papillons, mouches,... Chaque catégorie a de nombreuses sous-catégories, et sous ces sous-catégories se trouvent encore plus de sous-catégories.

Ce processus de classification est appelé taxonomie. C'est une bonne métaphore de départ pour le mécanisme d'héritage de la programmation orientée objet.

Les questions qu'un scientifique pose en essayant de classer un nouvel animal ou objet sont les suivantes : En quoi est-il similaire aux autres de sa classe générale ? En quoi est-ce différent ? Chaque classe différente a un ensemble de comportements et de caractéristiques la définissant. Un scientifique commence au sommet de l'arbre généalogique d'un spécimen et commence à descendre les branches, posant ces questions en cours de route. Les niveaux les plus élevés sont les plus généraux, et les questions les plus simples : Ailes ou pas ailes ? Chaque niveau est plus spécifique que le précédent et moins général. Finalement, le scientifique arrive au point de compter les poils sur le troisième segment des pattes postérieures de l'insecte, en effet spécifiques.

Le point important à retenir est qu'une fois qu'une caractéristique est définie, toutes les catégories sous cette définition incluent cette caractéristique. Ainsi, une fois que vous avez identifié un insecte comme membre de l'ordre des diptères (mouches), vous n'avez pas besoin de souligner qu'une mouche a une paire d'ailes. L'espèce d'insecte appelée mouches hérite de cette caractéristique.

Comme vous l'apprendrez bientôt, la programmation orientée objet est le processus de création d'arbres généalogiques pour les structures de données. L'une des choses importantes que la programmation orientée objet ajoute aux langages traditionnels comme Pascal est un mécanisme par lequel les types de données héritent des caractéristiques de types plus simples et plus généraux. Ce mécanisme est l'héritage.

Objets : enregistrements qui héritent

En termes Pascal, un objet ressemble beaucoup à un enregistrement, étant un enveloppe pour joindre plusieurs éléments de données liés sous un même nom. Supposons que vous vouliez développer un programme de paie produisant un rapport montrant combien chaque employé est payé chaque jour de paie. Vous pouvez mettre en page un enregistrement comme celui-ci :

  1. Type
  2.  TEmployee=Record
  3.   Name:String[25];
  4.   Title:String[25];
  5.   Rate:Real;
  6.  End;

TEmployee est ici un type d'enregistrement ; c'est-à-dire qu'il s'agit d'un modèle que le compilateur utilise pour créer des variables d'enregistrement. Une variable de type TEmployee est une instance de type TEmployee. Le terme instance est utilisé de temps en temps dans les cercles Pascal, mais il est utilisé tout le temps par les personnes POO, et vous feriez bien de commencer à penser en termes de types et d'instances de ces types.

Avec le type TEmployee, vous avez les deux sens : vous pouvez penser aux champs Name, Title et Rate séparément, ou lorsque vous devez penser aux champs travaillant ensemble pour décrire un travailleur particulier, vous pouvez les considérer collectivement comme TEmployee.

Supposons que vous ayez plusieurs types d'employés travaillant dans votre entreprise. Certains sont payés à l'heure, certains sont salariés, certains sont commissionnés,... Votre programme de paie doit tenir compte de tous ces types. Vous pouvez développer un type d'enregistrement différent pour chaque type d'employé. Par exemple, pour déterminer le salaire horaire d'un employé, vous devez savoir combien d'heures l'employé a travaillé. Vous pouvez concevoir un enregistrement THourly comme celui-ci :

  1. Type
  2.  THourly=Record
  3.   Name:String[25];
  4.   Title:String[25];
  5.   Rate:Real;
  6.   Time:Integer;
  7.  End;

Vous pouvez également être un peu plus malin et conserver le type d'enregistrement TEmployee en créant un champ de type TEmployee dans le type THourly :

  1. Type
  2.  THourly=Record
  3.   Worker:TEmployee;
  4.   Time:Integer;
  5.  End;

Cela fonctionne, et les programmeurs Pascal le font tout le temps. Une chose que cette méthode ne fait pas est de vous forcer à réfléchir à la nature de ce que vous manipulez dans votre logiciel. Vous devez poser des questions telles que : «En quoi un employé horaire diffère-t-il des autres employés ?» La réponse est la suivante : un employé horaire est un employé étant payé pour le nombre d'heures travaillées par l'employé. Repensez à la première partie de cette affirmation : un employé horaire est un employé...

Voilà !

Un enregistrement d'employé horaire doit avoir tous les champs existant dans l'enregistrement d'employé. Le type THourly est un type descendant du type TEmployee. THourly hérite de tout ce que possède TEmployee et ajoute tout ce qui est nouveau à propos de THourly pour rendre THourly unique. Ce processus par lequel un type hérite des caractéristiques d'un autre type est appelé héritage. L'héritier est appelé un type descendant; le type dont hérite le type descendant est un type ancêtre.

Les types d'enregistrement Pascal familiers ne peuvent pas hériter. Turbo Pascal, cependant, étend le langage Pascal pour prendre en charge l'héritage. L'une de ces extensions est une nouvelle catégorie de structure de données, liée aux enregistrements mais beaucoup plus puissante. Les types de données de cette nouvelle catégorie sont définis avec un nouveau mot réservé : Object. Un type d'objet peut être défini comme un type complet et autonome à la manière des enregistrements Pascal, ou il peut être défini comme un descendant d'un type d'objet existant en plaçant le nom du type ancêtre entre parenthèses après le mot réservé Object.

Dans l'exemple de paie que vous venez de regarder, les deux types d'objets liés seraient définis comme suit :

  1. Type
  2.  TEmployee=Object
  3.   Name:String[25];
  4.   Title:String[25];
  5.   Rate:Real;
  6.  End;
  7.  
  8.  THourly=Object(TEmployee)
  9.   Time:Integer;
  10.  End;

Ici, TEmployee est le type ancêtre et THourly est le type descendant. Comme vous le verrez un peu plus tard, le processus peut se poursuivre indéfiniment : vous pouvez définir des descendants de type THourly, et des descendants du type descendant de THourly,... Une grande partie de la conception d'une application orientée objet réside dans la construction de cette hiérarchie d'objets exprimant l'arbre généalogique des objets de l'application.

Tous les types héritant éventuellement de TEmployee sont appelés les types descendants de TEmployee, mais THourly est l'un des descendants immédiats de TEmployee. Inversement, TEmployee est l'ancêtre immédiat de THourly. Un type d'objet (tout comme un sous-répertoire DOS) peut avoir n'importe quel nombre de descendants immédiats, mais un seul ancêtre immédiat.

Les objets sont étroitement liés aux enregistrements, comme le montrent ces définitions. Le nouveau mot réservé object est la différence la plus évidente, mais il existe de nombreuses autres différences, dont certaines assez subtiles, comme vous le verrez plus tard.

Par exemple, les champs Name, Title et Rate de TEmployee ne sont pas explicitement écrits dans le type THourly, mais THourly les a quand même, en vertu de l'héritage. Vous pouvez parler de la valeur Name de THourly, tout comme vous pouvez parler de la valeur Name de TEmployee.

Instances de types d'objets

Les instances de types d'objets sont déclarées de la même manière que n'importe quelle variable est déclarée en Pascal, soit en tant que variables statiques, soit en tant que référents de pointeur alloués sur la mémoire de tas :

  1. Type
  2.  PHourly=^THourly;
  3.  
  4. Var
  5.  StatHourly:THourly; { Prêt à partir! }
  6.  DynaHourly:PHourly; { Doit être attribué avec New avant utilisation }

Les champs d'un objet

Vous accédez aux champs de données d'un objet de la même manière que vous accédez aux champs d'un enregistrement ordinaire, soit via l'instruction With, soit en pointant; Par exemple :

  1. BEGIN
  2.  AnHourly.Rate:=9.45;
  3.  With AnHourly do Begin
  4.   Name:='Sabourin, Arthur';
  5.   Title:='Traitement de texte';
  6.  End;
  7. END.

Il suffit de se rappeler dans un premier temps (cela vient naturellement éventuellement) que les champs hérités sont tout aussi accessibles que les champs déclarés dans un type d'objet donné. Par exemple, même si Name, Title et Rate ne font pas partie de la déclaration de THourly (ils sont hérités du type TEmployee), vous pouvez les spécifier comme s'ils étaient déclarés dans THourly :

  1. AnHourly.Name:='Arthur Sabourin';

Bonne pratique et mauvaise pratique

Même si vous pouvez accéder directement aux champs d'un objet, ce n'est pas particulièrement une bonne idée de le faire. Les principes de la programmation orientée objet exigent que les champs d'un objet soient laissés seuls autant que possible. Cette restriction peut sembler arbitraire et rigide au premier abord, mais elle fait partie de la vue d'ensemble de la POO étant construite dans cette page. Avec le temps, vous comprendrez le sens de cette nouvelle définition des bonnes pratiques de programmation, bien qu'il y ait encore du chemin à parcourir avant que tout ne se concrétise. Pour l'instant, prenez-le sur la foi : évitez d'accéder directement aux champs de données d'objet.

Alors, comment accède-t-on aux champs d'objet ? Qu'est-ce qui les fixe et les lit ?

La réponse est que les méthodes d'un objet sont utilisées pour accéder aux champs de données d'un objet chaque fois que cela est possible. Une méthode est une procédure ou une fonction déclarée dans un objet et étroitement liée à cet objet.

Méthodes

Les méthodes sont l'un des attributs les plus frappants de la programmation orientée objet, et il faut un certain temps pour s'y habituer. Commencez par revenir à cette vieille nécessité de la programmation structurée, en initialisant les structures de données. Considérez la tâche d'initialisation d'un enregistrement avec cette définition :

  1. Type
  2.  TEmployee=Record
  3.   Name:String[25];
  4.   Title:String[25];
  5.   Rate:Real;
  6.  End;

La plupart des programmeurs utiliseraient une instruction With pour attribuer des valeurs initiales aux champs Name, Title et Rate :

  1. Var
  2.  MyEmployee: TEmployee;
  3. BEGIN 
  4.  With MyEmployee do Begin
  5.   Name:='Arthur Sabourin';
  6.   Title:='Traitement de texte';
  7.   Rate:=9.45;
  8.  End;
  9. END.

Cela fonctionne bien, mais il est étroitement lié à une instance d'enregistrement spécifique, MyEmployee. Si plusieurs enregistrements TEmployee doivent être initialisés, vous en aurez besoin de plus avec des instructions faisant essentiellement la même chose. L'étape suivante naturelle consiste à construire une procédure d'initialisation généralisant l'instruction With pour englober toute instance d'un type TEmployee passé en paramètre :

  1. Procedure InitTEmployee(Var Worker:TEmployee; AName,ATitle:String;ARate:Real);Begin
  2.  With Worker do Begin
  3.   Name:=AName;
  4.   Title:=ATitle;
  5.   Rate:=ARate;
  6.  End;
  7. End;

Cela fait le travail, d'accord, mais si vous avez l'impression que c'est un peu plus idiot que ça ne devrait l'être, vous ressentez la même chose que les premiers partisans de la programmation orientée objet.

C'est un sentiment impliquant que vous avez conçu la procédure InitTEmployee spécifiquement pour servir le type TEmployee. Pourquoi, alors, devez-vous continuer à spécifier sur quel type d'enregistrement et quelle instance InitTEMPloyee agit ? Il devrait y avoir un moyen de fusionner le type d'enregistrement et le code le servant en un tout homogène.

Maintenant, il y a. Ça s'appelle une méthode. Une méthode est une procédure ou une fonction soudée si étroitement à un type donné que la méthode est entourée d'une instruction With invisible, rendant les instances de ce type accessibles depuis la méthode. La définition de type inclut l'entête de la méthode. La définition complète de la méthode est qualifiée par le nom du type. Le type d'objet et la méthode d'objet sont les deux faces de cette nouvelle espèce de structure appelée objet :

  1. Type
  2.  TEmployee=Object
  3.   Name,Title:String[25];
  4.   Rate:Real;
  5.   Procedure Init(NewName,NewTitle:String[25];NewRate:Real);
  6.  End;
  7.  
  8. Procedure TEmployee.Init(NewName, NewTitle:String[25];NewRate:Real);Begin
  9.  Name:=NewName;   { Le champ Name d'un objet TEmployee }
  10.  Title:=NewTitle; { Le champ Title d'un objet TEmployee }
  11.  Rate:=NewRate;   { Le champ Rate d'un objet TEmployee }
  12. End;

Maintenant, pour initialiser une instance de type TEmployee, vous appelez simplement sa méthode comme si la méthode était un champ d'un enregistrement, ce qui, dans un sens très réel, c'est :

  1. Var
  2.  AnEmployee:TEmployee;
  3.  
  4. BEGIN
  5.  AnEmployee.Init('Sonia Archambault, Gestionnaire de compte, 15000'); {Facile, non ? }
  6. END.


Dernière mise à jour : Jeudi, le 6 juillet 2023