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 :
- Type
- shape=Object
- color:colors;
- height,width:Integer;
- Procedure shape.init;
- Procedure shape.draw;
- Procedure shape.move(hoz,vert:Integer);
- Function shape.area:Integer;
- End;
-
- Procedure shape.init;Begin
- { code pour la méthode init ici }
- { : }
- { : }
- End;
-
- Procedure shape.draw;Begin
- { code pour la méthode draw ici }
- { : } { reste des méthodes }
- { : }
- 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 :
É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 :
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 :
- 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 :
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 :
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 :
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_ :
- Var
- 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 :
- 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 :
- my_circle.draw;
- my_circle.move(30, 30);
- 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 :
- If Member(a_circle,shape)Then
- 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 :
- 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 :
- Les classes et les objets sont précédés d'un «T» majuscule. Cela identifie la variable en tant que type d'objet, comme illustré ci-dessous :
- Tcircle=Object(Tshape)
- Les variables d'instance d'objet sont précédées d'un «f» minuscule. Le «f» indique en un coup d'oeil que l'identificateur est un champ. Les identificateurs sans
le "f" sont des méthodes, comme dans l'exemple suivant :
- the_radius:=Tcircle.fradius;
- Tcircle.draw;
- Les variables globales sont précédées d'un "g" minuscule. Ceci est utile pour identifier les objets pouvant recevoir des messages d'objets en dehors de leur propre classe (voir ci-dessous) :
- gTtemp_circle.color:=blue;
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 :
- Program ObjectDemo;
- { Démonstration des techniques d'objet avec des formes géométriques }
- {M+}
-
- Type
- geo_shape=Object
- area:Real;
- height:Real;
- what:String;
- Procedure geo_shape.init;
- Procedure geo_shape.say_what;
- Function geo_shape.get_area:Real;
- End;
-
- rectangle_=Object(geo_shape)
- len:Real;
- Function rectangle_.is_square:Boolean;
- Procedure rectangle_.init;Override;
- Function rectangle_.get_area:Real; Override;
- End;
-
- circle_=Object(geo_shape)
- radius:Real;
- Procedure circle_.init; Override;
- Function circle_.get_area:Real; Override;
- End;
-
- Procedure geo_shape.init;Begin
- Self.area:=0;
- Self.height:=0;
- Self.what:='Forme géométrique';
- End;
-
- Procedure geo_shape.say_what;Begin
- Writeln(Self.what);
- End;
-
- Function geo_shape.get_area:Real;Begin
- Self.area:=Self.height*Self.height;
- get_area:=Self.height;
- End;
-
- Procedure circle_.init;Begin
- Inherited Self.init;
- Self.radius:=4;
- Self.what:='cercle';
- End;
-
- Function circle_.get_area:Real;Begin
- Self.area:=(Pi*Sqr( Self.radius));
- get_area:=Self.area;
- End;
-
- Procedure rectangle_.init;Begin
- Inherited Self.init;
- Self.height:=5;
- Self.len:=5;
- Self.what:='Rectangle';
- End;
-
- Function rectangle_.is_square:Boolean;Begin
- is_square:=False;
- If Self.len=Self.height Then is_square:=True;
- End;
-
- Function rectangle_.get_area:Real;Begin
- Self.area:=(Self.len*Self.height);
- get_area:=Self.area;
- End;
-
- Var
- the_circle:circle_;
- the_rect:rectangle_;
-
- BEGIN
- New(the_circle);
- the_circle.init;
- New(the_rect);
- the_rect.init;
- the_circle.say_what;
- WriteLn('Région : ', the_circle.get_area);
- WriteLn;
- the_rect.say_what;
- WriteLn('Région : ',the_rect.get_area);
- Dispose(the_circle);
- Dispose(the_rect);
- END.
on obtiendra un résultat ressemblant à ceci :
cercleRégion : 5.02654824574129E+0001
Rectangle
Région : 2.50000000000000E+0001