Section courante

A propos

Section administrative du site

Programmation orientée objet

La programmation orientée objet est largement saluée comme le style de programmation du futur. Le QuickPascal vous propose aujourd'hui la programmation orientée objet, à travers ses extensions objet au Pascal standard. Bien qu'elles n'apportent que quelques ajouts syntaxiques au langage, les extensions d'objet QuickPascal fournissent un cadre puissant et efficace pour créer des programmes.

Aperçu

Les programmes Pascal standard, ainsi que les programmes écrits dans d'autres langages procéduraux, sont organisés autour d'un ensemble de structures de données, avec des procédures et des fonctions distinctes manipulant les données. Un exemple est un programme graphique déclarant chaque forme comme un TYPE unique. Diverses routines dessinent, effacent et déplacent les formes, en utilisant probablement une instruction CASE pour les différencier.

Les programmes orientés objet fonctionnent différemment. Au lieu d'être organisés autour de données, ils sont organisés autour d'un ensemble «d'objets». Un objet est une structure combinant à la fois des données et des routines en un seul type. Il est similaire à un type Pascal RECORD, mais peut entreposer à la fois des fonctions et des procédures ainsi que des données.

Les objets ont une propriété appelée "héritage". Une fois qu'un objet a été déclaré, un autre objet peut être dérivé héritant de toutes les données et routines associées au type parent. De nouvelles données et routines peuvent être ajoutées ou des routines héritées existantes peuvent être modifiées.

Un programme graphique ayant été écrit avec des extensions d'objet pour QuickPascal déclarerait un objet initial de "forme générique". La forme générique définirait toutes les données et routines, telles que le dessin, l'effacement et la taille, communes à chaque forme. De nouvelles formes seraient dérivées de la forme générique, puis ces nouvelles formes déclareraient des champs de données supplémentaires, remplaceraient les routines existantes et en ajouteraient de nouvelles.

L'un des principaux avantages de la programmation orientée objet est la facilité avec laquelle les programmes peuvent être modifiés et des parties réutilisées. Dans l'application graphique Pascal standard hypothétique, pour ajouter une forme octogonale au programme, vous auriez besoin de déclarer un tout nouveau type ainsi que de modifier chaque routine traitant des formes. Avec les extensions d'objet de QuickPascal, vous définiriez un objet octogone, déjà dérivé de l'objet de forme générique, et ajouteriez ou modifieriez toutes les données ou routines utilisées exclusivement par l'octogone. Les anciennes routines fonctionneraient toujours de la même manière sur les anciens types d'objets. Au lieu d'apporter des modifications à l'ensemble du programme, toutes les modifications se produiraient dans une zone localisée et ne s'appliqueraient qu'à cet objet ou à ses descendants.

L'exemple de la section suivante illustre les techniques de base de la programmation orientée objet.

Concepts de programmation objet

Les extensions orientées objet sont basées sur quatre concepts : les classes, les objets, les méthodes et l'héritage. Une "classe" est similaire à un RECORD de Pascal. Il décrit une structure globale pour un nombre quelconque de types basés sur celle-ci. La principale différence entre une classe et un enregistrement est qu'une classe combine des champs de données (appelés «variables d'instance») et des procédures et fonctions (appelées «méthodes») agissant sur les données. Les variables d'instance peuvent inclure des types de données Pascal standard ainsi que des objets.

Un «objet» est une variable d'une classe (souvent appelée instance de classe). Comme une classe, un objet est déclaré comme un TYPE. Tous les objets dérivés d'une classe sont considérés comme des membres de cette classe et partagent des caractéristiques similaires de la superclasse.

Les "méthodes" sont des procédures et des fonctions encapsulées dans une classe ou un objet. L'appel d'une méthode s'appelle "transmettre un message à un objet". Les extensions d'objet de QuickPascal créent des programmes effectuant la majeure partie de leur travail en envoyant des messages aux objets et en demandant aux objets de s'envoyer des messages les uns aux autres. Les méthodes sont stockées dans une table de méthodes de type objet et n'occupent pas de mémoire lorsqu'un objet est déclaré en tant que variable.

Les membres d'une même classe présentent un comportement similaire par héritage. Cela signifie que les instances de variables et les méthodes trouvées dans une superclasse sont également présentes dans les objets dérivés de la superclasse. De plus, les objets ont leur propre espace pour entreposer des données et des méthodes locales à l'objet. Si nécessaire, un objet peut également remplacer la méthode d'une classe parente, en remplaçant les instructions de la méthode héritée par les siennes. Si c'est le cas, seules les méthodes de l'objet descendant sont modifiées, tandis que celles du parent restent inchangées.

Utiliser des objets

Comme mentionné précédemment, les extensions d'objet de QuickPascal n'ajoutent que quelques nouveaux mots-clefs et types. Tous les identificateurs, constructions et routines Pascal standard sont disponibles lors de la programmation avec des objets. Les différences dans l'utilisation des extensions d'objet résident dans les domaines de la déclaration des structures de données de classe et d'objet et de l'appel de procédures et de fonctions via des méthodes.

Définition de la directive du compilateur de méthodes

La première étape de l'utilisation des extensions d'objet consiste à activer la directive du compilateur Method. La directive {$M+} doit apparaître au début de tout fichier source utilisant des objets. (La directive {$M+} est activée par défaut.) Cette directive indique au compilateur de vérifier si la mémoire d'un objet a été allouée ou non avant l'exécution de la méthode de l'objet.

Création de classes

Comme tous les objets sont dérivés de classes, les classes sont créées en premier. Une classe doit incorporer toutes les données et méthodes que les objets descendants auront en commun.

Vous utilisez la syntaxe suivante pour déclarer une classe d'objet :

Type
 ClassName=Object
  DataFields
 {Procedure|Function}|[Methods]
End;

Les parties de la syntaxe sont définies ci-dessous :

Paramètre Description
ClassName Un nom unique identifiant la classe.
OBJECT Un mot-clef QuickPascal demandant au compilateur de traiter la structure comme un objet.
DataFields La déclaration d'une ou plusieurs structures de données. La syntaxe est la même que celle utilisée pour déclarer les champs d'un enregistrement.
Methods Une liste de déclarations de méthode. Chaque déclaration de méthode est comme un en-tête de procédure ou de fonction, sauf que le nom peut être qualifié par le nom de la classe : ClassName.MethodName. Bien que non obligatoire, une telle qualification est un bon style de programmation. Les méthodes sont déclarées immédiatement après les déclarations de classe et de type d'objet.

Par exemple, le fragment de code suivant crée une forme générique pour un programme graphique :

  1. Type
  2.  shape=Object
  3.   color:colors;
  4.   height,width:Integer;
  5.   Procedure shape.init;
  6.   Procedure shape.draw;
  7.   Procedure shape.move(hoz,vert:Integer);
  8.   Function shape.area:Integer;
  9.  End;
  10.  
  11. Procedure shape.init;Begin 
  12.  { code pour la méthode init ici }
  13.  { : }
  14.  { : } 
  15. End;
  16.  
  17. Procedure shape.draw;Begin 
  18.  { code pour la méthode draw ici }
  19.  { : } { reste des méthodes }
  20.  { : } 
  21. END;

Création de sous-classes

Une fois qu'une classe a été créée, des sous-classes peuvent être définies. La syntaxe de création d'une sous-classe est similaire à celle d'une classe :

Type
 ObjectName=Object(ParentClass)
  DataFields
   {PROCEDURE|FUNCTION)([Methods]) [[; OVERRIDE]
End;

Les deux aspects particuliers de la déclaration d'objets sont l'utilisation de la classe parent et la redéfinition des méthodes héritées. Le paramètre ParentClass est le nom d'une classe parent. Étant donné que la sous-classe est dérivée d'une classe, vous placeriez le nom de la classe entre parenthèses.

Si la sous-classe redéfinit une méthode à partir de la classe parent, l'instruction OVERRIDE doit apparaître après l'entête de la méthode.

Par exemple, le fragment de code suivant déclare un descendant de la classe shape :

  1. Type
  2.  circle_=Object(shape)
  3.   radius:Integer;
  4.   Procedure circle_.init; Override;
  5.   Procedure circle_.draw; Override;
  6.   Function circle_.area: Integer; Override;
  7.   Procedure circle_.changeradius(new_radius:Integer);
  8.  End;

Étant donné que le type de cercle est dérivé de la classe shape, il n'est pas nécessaire de déclarer toutes les variables d'instance et les méthodes de shape. Les seules variables et méthodes qui doivent être déclarées sont celles qui sont nouvelles et exclusives à l'objet circle_. Dans ce cas, les nouveaux éléments sont le champ radius et la méthode changeradius. Un objet circle_ aura des champs de couleur, hauteur, largeur et rayon.

Étant donné que les méthodes init, draw et area seront différentes pour circle_ et pour shape, le mot clef OVERRIDE demande au compilateur d'utiliser la méthode local à circle_ lorsqu'un de ces messages est passé à l'objet.

Définir des méthodes

Une fois qu'une méthode a été associée à un objet, elle doit être définie. Les méthodes sont définies avec les mots clefs PROCEDURE ou FUNCTION. Les instructions réelles composant la méthode sont définies après la création de toutes les classes et sous-classes. Le mot clef PROCEDURE ou FUNCTION précède le nom de l'objet, suivi d'un point (.) et du nom de la méthode. Les méthodes étant remplacées suivent la même syntaxe. (Voir l'exemple en fin de page.)

La première méthode que vous devez définir est celle qui initialise tous les champs de données de l'objet, alloue de la mémoire ou effectue toute autre action dont l'objet peut avoir besoin avant d'être utilisé. Cette méthode doit être appelée immédiatement après que l'espace a été alloué à l'objet.

Les variables d'instance qui appartiennent à l'objet sont accessibles depuis une méthode en utilisant leur identificateur précédé de la pseudo-variable Self, comme illustré ci-dessous :

  1. Procedure circle_.init;Begin
  2.  Self.color:=Blue;
  3.  Self.height:=20;
  4.  Self.width:=20;
  5.  Self.radius:=0;
  6. End;

Le Self ordonne simplement à l'objet d'opérer sur lui-même.

Les données d'un objet peuvent être accédées directement par un programme, comme si l'objet était un enregistrement :

  1. the_radius:=circle_.radius;

Aussi, pour appeler une méthode appartenant à l'objet depuis une autre méthode, vous pouvez la faire précéder de la variable Self. Dans le fragment de code ci-dessous, Self.draw est équivalent à circle_.draw :

  1. Procedure circle_.move(hoz,vert:Integer);Begin
  2.  Self.draw;
  3. End;

Notez que vous n'êtes pas limité uniquement à l'utilisation de méthodes lorsque vous utilisez des extensions d'objet pour QuickPascal. Les méthodes ne sont utilisées qu'avec des objets. Les procédures et fonctions Pascal standard peuvent être implémentées pour manipuler d'autres formes de données.

Utilisation de INHERITED

Le mot clef INHERITED annule un remplacement d'une méthode héritée. Si la classe de méthode n'exécute qu'une partie de ce qu'un objet doit avoir fait, la méthode parent peut être appelée à partir de la méthode descendante. Par exemple, supposons que dans la méthode d'initialisation de la forme, vous définissiez les valeurs suivantes :

  1. Procedure shape.init;Begin
  2.  color:=Blue;
  3.  height:=20;
  4.  width:=20;
  5. End;

Si l'objet cercle utilise ces valeurs, mais remplace la méthode pour initialiser son propre champ de données, INHERITED peut être utilisé pour appeler la méthode ancêtre. Cela initialiserait les champs communs sans avoir besoin de les initialiser dans la méthode descendante :

  1. Procedure circle_.init;Begin
  2.  radius:=0;
  3.  Inherited Self.init;
  4. End;

Déclarer des objets

La déclaration d'un objet est similaire à la déclaration d'une variable dynamique. La syntaxe est :

Var
 ObjectIdentifier:Class

L'ObjectIdentifier est l'identificateur QuickPascal de l'objet et Class est le type de l'objet.

Par exemple, ce code déclare un objet de la classe circle_ :

  1. Var
  2.  my_circle:circle_;

Allocation de mémoire

Avant qu'un objet puisse être utilisé dans un programme, un espace mémoire doit lui être alloué. Cela se fait avec la procédure New de Pascal :

  1. New(my_circle);

Une erreur courante dans la programmation orientée objet est d'oublier d'allouer de la mémoire à un objet, puis d'essayer d'y accéder. L'allocation de mémoire pour les objets devrait être l'une des premières actions du corps du programme.

Méthodes d'appel

Une fois que les classes et les objets ont été déclarés et que la mémoire a été allouée à l'objet, vous pouvez appeler une méthode (c'est-à-dire envoyer un message à l'objet) depuis le corps principal du programme pour manipuler les variables d'instance de l'objet. L'envoi d'un message est similaire à l'appel d'une procédure ou d'une fonction en Pascal standard. La seule différence est que vous spécifiez à la fois l'objet et la méthode.

Par exemple, différentes méthodes pour my_circle sont exécutées par :

  1. my_circle.draw;
  2. my_circle.move(30, 30);
  3. circle_area:=my_circle.area;

Tester l'adhésion

La fonction Member teste si un objet particulier se trouve dans une classe, comme illustré ci-dessous :

  1. If Member(a_circle,shape)Then
  2.  num_shapes:=num_shapes+1;

La fonction reçoit l'objet et la classe. Elle renvoie True si l'objet est une instance ou un descendant de la classe.

Disposer d'objets

Lorsque vous avez fini d'utiliser un objet, la mémoire lui étant allouée doit être libérée. Cela se fait avec la procédure Dispose :

  1. Dispose(my_circle);

Avant de vous débarrasser d'un objet, assurez-vous qu'il ne sera plus utilisé dans le cadre du programme.

Souvent, une méthode libre est déclarée pour réallouer la mémoire de la structure de données, fermer les fichiers et effectuer d'autres opérations de nettoyage. Une telle méthode doit être appelée avant d'utiliser Dispose.

Stratégies de programmation objet

La plus grande difficulté rencontrée par les programmeurs apprenant les extensions objet de QuickPascal est la nécessité de planifier et d'écrire leurs programmes d'une manière orientée objet. Trop souvent, les premiers programmes orientés objet d'un programmeur présentent un style procédural avec des objets saupoudrés au hasard. La programmation de cette manière réduit la valeur des extensions d'objet pour produire un code efficace et réutilisable. Les sections suivantes traitent de plusieurs problèmes que vous devez garder à l'esprit lorsque vous créez des programmes orientés objet.

Conventions de style d'objet

Bien que les conventions de style pour les programmes soient souvent une question de préférence personnelle, l'adoption de certaines conventions de style pour la programmation objet peut rendre votre code source plus facile à lire. Par exemple, étant donné qu'un enregistrement Pascal et un objet utilisent tous deux un point (.) pour accéder à leurs champs de données et méthodes, il peut être difficile de distinguer les objets des enregistrements. Ceci est rendu plus compliqué par la difficulté de dire si un identificateur suivant un objet est une variable d'instance ou une méthode.

Voici quelques conventions de style pour la programmation objet :

Réutilisabilité des objets

L'essence de la programmation orientée objet est la réutilisabilité. Lorsque vous créez des objets, vous devez réfléchir à leur utilisation future, à la fois pour le programme en cours et pour les programmes ultérieurs. Il est préférable de créer des classes à partir desquelles d'autres objets peuvent être dérivés. L'héritage des méthodes est généralement plus important que l'héritage des données. À plus grande échelle, les bibliothèques de classes sont utiles pour traiter les tâches courantes. Un ensemble de classes liées peut résider dans un UNIT et être appelé à tout moment.

Modularité

Les extensions d'objet de QuickPascal se prêtent à des programmes modularisés. Les méthodes d'une classe peuvent facilement être conservées ensemble, au lieu d'être réparties dans le code source. Un programme objet correctement construit ne devrait nécessiter que quelques modifications localisées pour ajouter et modifier des méthodes.

Méthodes

Les méthodes doivent être traitées comme des composantes remplaçables des blocs de construction d'objets de QuickPascal. Les méthodes sont conçues pour servir un seul objectif; une méthode polyvalente est plus difficile à modifier car elle exécute une variété de tâches. Chaque fois que vous souhaitez effectuer une action, créez une méthode pour le faire. Les méthodes doivent être courtes, au maximum plusieurs dizaines d'instructions.

Champs de données

Il n'est pas nécessaire de déclarer une variable d'instance pour chaque élément de données qu'un objet peut utiliser. Si plusieurs méthodes d'objet utilisent un élément de données spécifique, les données doivent être incorporées en tant que variable d'instance. Si une seule méthode accède aux données, elle peut être passée en paramètre à la méthode. Vous devez utiliser des variables d'instance d'objet à la place des variables globales pour favoriser la modularité.

Exemple de programme

Cet exemple montre comment fonctionne un programme orienté objet typique. Une classe est déclarée (geo_shape), avec deux sous-classes (rectangle_ et circle_). Les deux sous-classes montrent comment ajouter des variables d'instance et des méthodes et comment remplacer les méthodes parentes existantes. Le corps du programme OBJECTDE.PAS contient des exemples de définition de méthodes, d'accès à des variables d'instance, de déclaration et de suppression d'objets :

  1. Program ObjectDemo;
  2.  { Démonstration des techniques d'objet avec des formes géométriques }
  3. {M+}
  4.  
  5. Type
  6.  geo_shape=Object
  7.   area:Real;
  8.   height:Real;
  9.   what:String;
  10.   Procedure geo_shape.init;
  11.   Procedure geo_shape.say_what;
  12.   Function geo_shape.get_area:Real;
  13.  End;
  14.  
  15.  rectangle_=Object(geo_shape)
  16.   len:Real;
  17.   Function rectangle_.is_square:Boolean;
  18.   Procedure rectangle_.init;Override;
  19.   Function rectangle_.get_area:Real; Override;
  20.  End;
  21.  
  22.  circle_=Object(geo_shape)
  23.   radius:Real;
  24.   Procedure circle_.init; Override;
  25.   Function circle_.get_area:Real; Override;
  26.  End;
  27.  
  28. Procedure geo_shape.init;Begin
  29.  Self.area:=0;
  30.  Self.height:=0;
  31.  Self.what:='Forme géométrique';
  32. End;
  33.  
  34. Procedure geo_shape.say_what;Begin
  35.  Writeln(Self.what);
  36. End;
  37.  
  38. Function geo_shape.get_area:Real;Begin
  39.  Self.area:=Self.height*Self.height;
  40.  get_area:=Self.height;
  41. End;
  42.  
  43. Procedure circle_.init;Begin
  44.  Inherited Self.init;
  45.  Self.radius:=4;
  46.  Self.what:='cercle';
  47. End;
  48.  
  49. Function circle_.get_area:Real;Begin
  50.  Self.area:=(Pi*Sqr( Self.radius));
  51.  get_area:=Self.area;
  52. End;
  53.  
  54. Procedure rectangle_.init;Begin
  55.  Inherited Self.init;
  56.  Self.height:=5;
  57.  Self.len:=5;
  58.  Self.what:='Rectangle';
  59. End;
  60.  
  61. Function rectangle_.is_square:Boolean;Begin
  62.  is_square:=False;
  63.  If Self.len=Self.height Then is_square:=True;
  64. End;
  65.  
  66. Function rectangle_.get_area:Real;Begin
  67.  Self.area:=(Self.len*Self.height);
  68.  get_area:=Self.area;
  69. End;
  70.  
  71. Var
  72.  the_circle:circle_;
  73.  the_rect:rectangle_;
  74.  
  75. BEGIN
  76.  New(the_circle);
  77.  the_circle.init;
  78.  New(the_rect);
  79.  the_rect.init;
  80.  the_circle.say_what;
  81.  WriteLn('Région : ', the_circle.get_area);
  82.  WriteLn;
  83.  the_rect.say_what;
  84.  WriteLn('Région : ',the_rect.get_area);
  85.  Dispose(the_circle);
  86.  Dispose(the_rect);
  87. END.

on obtiendra un résultat ressemblant à ceci :

cercle
Région : 5.02654824574129E+0001

Rectangle
Région : 2.50000000000000E+0001


Dernière mise à jour : Dimanche, le 28 août 2022