Programmation orientée objet
La programmation orientée objet est une fonctionnalité importante de MPW Pascal. Cette page couvre brièvement la philosophie derrière la programmation orientée objet et les mécanismes intégrés à MPW Pascal la prenant en charge. Pour un traitement plus complet de la théorie de la programmation orientée objet, voir le livre de Kurt Schmucker Programmation orientée objet pour Macintosh.
Que sont les objets ?
Le type d'objet est un ajout aux types structurés Pascal standard familiers. Les objets sont étroitement liés aux enregistrements; comme un enregistrement, un objet est constitué d'un certain nombre de champs, chacun pouvant être d'un type différent. Les objets ajoutent une «dimension» supplémentaire à l'idée d'un enregistrement : ils incluent non seulement des champs de données mais également des procédures et des fonctions privées (appelées méthodes). Une méthode déclarée dans une définition de type d'objet fonctionne principalement sur les données entreposées dans un objet de ce type.
Une grande partie de la puissance de la programmation orientée objet découle du concept d'héritage. Vous pouvez définir un type d'objet comme personnalisation d'un autre type d'objet. Lorsqu'un type d'objet est dérivé d'un autre, le premier est appelé ancêtre et le second est appelé descendant. Un type descendant hérite de tous les champs et méthodes de son ancêtre ; vous pouvez également y ajouter de nouveaux champs et méthodes. Bien que vous ne puissiez pas modifier l'entrelacement en méthode héritée, vous pouvez modifier la façon dont il est implémenté.
Voici un exemple de trois déclarations de types d'objet :
- Type
- Employee=Object
- FirstName,LastName:String[32];
- HourlyWage,HoursPaid:Integer;
- Procedure Hire(name1,Name2:String[32];Rate,HoursWorked:Integer);
- Function RegularPay(HoursWorked:Integer):Integer;
- Procedure IssuePaycheck(hoursWorked:Integer)
- End;
-
- ExemptEmployee=Object(Employee)
- Function RegularPay(hoursWorked:Integer):Integer;
- Override;
- End;
-
- Executive=Object(ExemptEmployee)
- WeeklyBonus:Integer;
- Procedure SetBonus(performanceLevel:Integer);
- Procedure IssuePaycheck(hoursWorked:Integer);Override;
- End;
Dans cet exemple, le type d'objet Employee commence la chaîne de caractères d'héritage. Employee n'est défini en termes d'aucun autre objet : il n'a pas d'ancêtres. Comme un enregistrement, un objet de type Employee est constitué de plusieurs champs de données. Mais en plus des données, un objet Employee dispose de trois méthodes : les procédures Hire et IssuePaycheck et la fonction RegularPay. Ces méthodes sont déclarées comme s'il s'agissait de routines FORWARD; leurs blocs arrivent plus tard dans le programme.
Le type d'objet ExemptEmployee est un descendant de Employee. Dans celui-ci, la fonction RegularPay est modifiée en remplaçant le corps de la fonction. Cependant, il hérite inchangé de tous les champs de données de Employee, ainsi que des procédures Hire et IssuePaycheck.
L'objet Executive est un descendant d'ExemptEmployee. Il conserve les champs de données de ExemptEmployee (étant également des champs de Employee) et ajoute le champ weeklyBonus. Il ajoute également une nouvelle procédure, SetBonus. De plus, il modifie la méthode IssuePaycheck en la remplaçant. Cela permet à IssuePaycheck d'opérer sur la valeur de weeklyBonus.
C'est une caractéristique importante de ce type de programmation que les déclarations de types d'objets n'ont pas besoin d'inclure des déclarations se produisant à l'intérieur de leurs objets ancêtres; ces déclarations sont automatiquement présentes par héritage. Ils doivent déclarer uniquement les modifications. Par exemple, les types d'objet ExemptEmployee et Executive incluent automatiquement la procédure Hire, car ils en ont hérité de Employee. Par conséquent, vous pouvez créer un type d'objet très complexe avec quelques déclarations simples, simplement en nommant un autre type d'objet dont il hérite de sa structure.
Différences avec la programmation traditionnelle
Vous pouvez examiner les différences entre la programmation orientée objet et la programmation standard de plusieurs manières :
- d'un point de vue du code, en termes de structures de programme que vous créez
- d'un point de vue des données, en termes de structures de données qu'ils gèrent
- d'un point de vue structurel, en termes de discipline de programmation en résultant.
Du point de vue du code, chaque objet d'un programme orienté objet peut être considéré comme un petit ordinateur virtuel, existant indépendamment au sein de l'ordinateur global. Il opère sur les données qui le traversent selon ses propres règles. Pour changer ces règles, il faut «reprogrammer» l'objet en changeant ses méthodes.
Du point de vue des données, les objets d'un programme orienté objet peuvent également être considérés comme des «structures de données intelligentes». Chacun non seulement stocke l'information mais la traite également, un peu à la manière d'un tableur. Étant donné que chaque objet opère sur les informations qu'il contient, vous pouvez traiter ses champs de données comme étant interdépendants plutôt que isolés.
Structurellement, l'orientation objet introduit un nouveau type de discipline de programmation. Lors de la création d'un programme standard, vous «concevez et codez». Vous déterminez d'abord les blocs dont votre programme a besoin, puis vous construisez les blocs requis à partir de déclarations et d'instructions individuelles. La programmation orientée objet vous permet de concevoir et de coder en même temps. Si vous disposez déjà d'un objet correspondant presque à vos besoins, vous commencez à coder avec. Au lieu de créer de nouveaux blocs à partir de pièces élémentaires, vous les créez en modifiant les blocs existants. Vous sculptez et façonnez, au lieu d'assembler. Le processus orienté objet est plus proche de la programmation d'applications de haut niveau et plus éloigné de la programmation machine. En conséquence, la programmation orientée objet présente un certain nombre d'avantages pratiques :
- Vous pouvez utiliser des objets de programmes existants pour créer de nouveaux programmes, au lieu de toujours les reconstruire. C'est un gain de temps considérable, en particulier lorsque vous développez des applications volumineuses.
- Lorsque vous modifiez des objets, vous commencez avec quelque chose fonctionnant déjà et modifiez son fonctionnement par incrémentations facilement compréhensibles. Cela peut réduire considérablement le temps de débogage.
- Chaque objet reste un univers fermé; vous n'avez pas à vous soucier des fuites de données ou des interactions de code entre les objets. Cela rend le développement du programme plus ordonné.
- Dans un environnement complexe, tel que celui du Macintosh, les programmes orientés objet sont plus faciles à maintenir. Les modifications apportées au programme entraînent des conséquences relativement spécifiques et prévisibles.
Création d'objets
Vous ne créez pas d'objets à partir de types d'objet de la même manière que vous créez des variables ordinaires à partir d'autres types de variables. Au lieu de cela, vous utilisez la procédure New standard pour créer un objet d'un type donné. La procédure New réserve une partie de la mémoire dynamique pour l'objet et renvoie un descripteur pour l'objet.
Remarque : Il s'agit d'une extension de la procédure standard Pascal New. Lorsque New reçoit une variable de pointeur ordinaire, il réserve de l'espace pour une variable identifiée et renvoie un pointeur vers la variable identifiée. Lorsque New reçoit une variable de type objet, il crée l'objet dans la mémoire dynamique et renvoie un descripteur vers l'objet.
Déclaration des types d'objets
Contrairement aux autres types, un type d'objet ne peut être déclaré que dans la partie de déclaration de type d'un programme ou d'une unité principale. Vous ne pouvez pas déclarer un type d'objet dans une partie de déclaration de variable ou dans un bloc de déclaration de procédure ou de fonction.
Lorsque vous utilisez un type d'objet dans une partie de déclaration de variable, vous créez une variable de référence pour ce type. La variable de référence entrepose un descripteur vers un objet de ce type (ou un type descendant). Vous accédez toujours aux objets via des variables de référence. Par conséquent, vous ne créez pas de types spéciaux pour les références d'objet. Au lieu de cela, un type d'objet est utilisé pour déclarer chaque variable pouvant contenir une référence à un objet de ce type (ou à un type descendant).
La portée d'un type d'objet (l'identificateur de type et les identificateurs de champ et de méthode) s'étend également à tous les descendants de ce type, ainsi qu'aux blocs de procédure et de fonction implémentant les méthodes de ce type d'objet et de ses descendants.
Pour une illustration des déclarations de type d'objet, voir l'exemple donné au début de cette page.
Appartenance au type d'objet
Lorsqu'un objet d'un type spécifique est créé lors de l'exécution d'un programme, il est considéré comme membre de ce type et de tous les types ancestraux. Dans l'exemple au début de la page, un objet créé en tant que Executive est membre des types Executive, ExemptEmployee et Employee. Les références à l'objet de type Executive peuvent être affectées à des variables de référence d'objet de types Executive, ExemptEmployee et Employee. La version particulière d'une méthode remplacée étant exécutée lorsqu'un appel de méthode est exécuté dépend du type de l'objet et non du type de la variable de référence.
Variables de référence d'objet
Une variable déclarée comme type d'objet est une variable de référence d'objet. Une variable de référence d'objet n'est pas elle-même un objet. La valeur d'une variable de référence d'objet est soit NIL, soit une valeur identifiant un objet, appelé objet identifié de la référence. Les objets eux-mêmes sont des variables dynamiques. Une variable de référence d'objet faisant référence à un objet le fait au moyen d'un descripteur : un pointeur vers un pointeur.
Le symbole de pointeur (^) utilisé pour désigner la variable identifiée d'un pointeur n'est pas autorisé après une variable de référence d'objet. De même, le symbole de double pointeur (^^) utilisé pour déréférencer un descripteur n'est pas autorisé. Il n'existe donc aucun moyen de traiter l'objet identifié comme une variable à part entière à moins d'utiliser la coercition de type. Cependant, vous pouvez accéder aux composants de l'objet via une variable de référence comme vous accéderiez aux champs d'un enregistrement.
Les données sont entreposées dans des champs d'objets et vous accédez à ces champs en donnant l'identifiant de la variable de référence, un point et le nom du champ, apparaissant de la même manière qu'un accès au champ d'enregistrement. Comme pour les enregistrements, vous pouvez omettre l'identificateur et la période de la variable de référence dans certaines circonstances. Les accès aux variables, en utilisant uniquement l'identificateur de la variable de référence, accèdent à la valeur du type de pointeur entreposée dans la variable de référence, tout comme avec les autres pointeurs.
Remarque : Le compilateur MPW est désormais plus strict en matière de signalement des erreurs liées à la transmission de pointeurs de référence vers des champs dans des objets.
La variable de référence de l'objet et le point (.) peuvent être omis dans une instruction WITH répertoriant la variable de référence, ou dans n'importe quel bloc de méthode déclarant une méthode du type de l'objet.
Remarque : L'utilisation d'une instruction WITH avec une variable de référence d'objet ne déréférence pas le descripteur représentant l'objet; cependant, les trois actions suivantes déréférencent le descripteur de l'objet : transmettre les champs d'un objet en tant que paramètres VAR, transmettre des champs de plus de quatre octets en tant que paramètres ou utiliser une instruction WITH pour un champ d'un objet étant lui-même un enregistrement (mais pas un référence d'objet). Le compactage de mémoire de tas peut entraîner le déplacement du descripteur de l'objet et produire des résultats imprévisibles. Le compilateur Pascal signale ces déréférencements d'objets dangereux comme des erreurs, à moins que la directive du compilateur $H ne soit désactivée.
Voici un exemple de la manière dont une variable déclarée comme type d'objet Employee peut être utilisée pour faire référence à un objet de type Executive, en utilisant les déclarations de type au début de cette page :
La directive OVERRIDE
Un descendant d'un type d'objet hérite toujours de tous les champs et méthodes de ses ancêtres. Il peut ajouter des champs et des méthodes à ceux dont il a hérité, et il peut remplacer l'action des méthodes. Pour remplacer une méthode, vous suivez l'entête de la méthode avec le mot OVERRIDE. Lorsqu'une méthode est substituée, l'implémentation de la méthode est modifiée mais l'interface de la méthode doit rester exactement la même. Il doit conserver la même orthographe de tous les identificateurs et les mêmes types de données pour les paramètres formels de la méthode et la valeur de retour (le cas échéant).
Déclaration des méthodes
Une méthode est une procédure ou une fonction déclarée dans le cadre d'une déclaration de type d'objet. Les méthodes sont déclarées comme les autres procédures ou fonctions, sauf qu'elles sont toujours déclarées dans le style d'une déclaration FORWARD mais sans le mot FORWARD. Les types d'objets sont souvent déclarés dans la partie interface d'une unité, tandis que les blocs de leurs méthodes sont déclarés dans la partie implémentation de la même unité.
Un type d'objet peut hériter des méthodes d'un autre type d'objet. Si vous souhaitez remplacer l'action d'une méthode héritée, écrivez le mot OVERRIDE après la liste des paramètres formels (ou après l'identifiant de la méthode, s'il n'y a pas de paramètres formels). La liste formelle des paramètres que vous donnez pour la méthode remplacée doit être identique à la liste formelle des paramètres de la méthode remplacée. Si vous ne remplacez pas une méthode héritée, vous n'avez pas besoin de déclarer la méthode héritée.
Lors de la déclaration d'une méthode, la déclaration d'entête initiale et la déclaration de bloc doivent toutes deux apparaître dans le programme principal ou doivent toutes deux apparaître dans la même unité de compilation. La déclaration d'entête apparaît dans la partie déclaration de type, dans le cadre de la déclaration de type d'objet, tandis que la déclaration du bloc de méthode apparaît dans la partie déclaration de procédure et de fonction. Vous devez écrire le type d'objet et un point ainsi que l'identificateur de la procédure ou de la fonction lorsque vous déclarez le bloc. Vous pouvez répéter la liste formelle des paramètres; si vous le faites, elle doit être identique à la liste originale.
Le paramètre Self
En plus des paramètres ordinaires, chaque méthode possède un paramètre implicite, appelé self. Self est une référence à l'objet utilisé pour appeler la méthode. Son type est le type référence du type d'objet auquel appartient la méthode. (Notez qu'il ne s'agit pas nécessairement du type du paramètre réel fourni par l'appelant. Self pourrait faire référence à un objet de type descendant.) La portée de Self s'étend sur le bloc de déclaration de méthode. Vous pouvez attribuer des valeurs aux champs de Self, mais vous ne pouvez pas modifier la valeur de Self, ce qui amènerait Self à référencer un autre objet. La méthode agit comme si toute sa partie instruction était entourée de WITH Self Do BEGIN ... END, vous n'avez donc pas besoin de donner l'identificateur Self lors de l'accès aux champs ou aux méthodes de Self.
Cette définition de type d'objet utilise le paramètre implicite Self dans une déclaration de méthode&nbps;:
Méthodes d'appel
Un appel de méthode est un cas particulier d'appel de fonction. Les mêmes règles s'appliquent aux méthodes et aux appels de méthodes pour les procédures et les fonctions. Comme pour les champs d'objets ou d'enregistrements, une méthode est accessible en utilisant un qualificateur ou en utilisant une instruction WITH. Voici quelques exemples d'appels de méthodes procédurales :
Voici quelques exemples d'appels de méthodes fonctionnelles :
Contrairement à un champ, pouvant être évalué ou affecté d'une nouvelle valeur, une méthode est exécutée lorsqu'elle est appelée. Lorsqu'une méthode est appelée, une référence à l'objet spécifique via lequel la méthode a été accédée (par exemple, l'objet v dans v.Draw) est liée au paramètre formel déclaré automatiquement self, dont le type est le type d'objet dont la méthode est partie.
Si y est déclaré comme étant une variable de type d'objet TView, alors à tout moment pendant l'exécution, y peut être une référence à un objet de n'importe quel type héritant de TView. Si TView a une méthode Draw, par exemple, différents types d'héritage peuvent définir différentes implémentations de la méthode Draw. L'exécution de y.Draw appelle l'implémentation de Draw défini pour le type d'objet auquel y fait référence au moment de l'appel. La valeur de self dans la méthode appelée devient une référence à ce même objet. Notez que y peut être une variable d'un type différent - la méthode réellement appelée est celle appartenant à l'objet, pas à la variable.
Une façon de lire l'instruction y.Draw est "Dites à y de dessiner". Le lire de cette façon souligne que le programme dit seulement que l'objet référencé par y doit dessiner. Au moment de l'exécution, la valeur actuelle de y détermine la manière dont il doit dessiner en choisissant l'implémentation appropriée de draw pour son type d'objet.
La directive INHERITED
Lorsqu'un type d'objet hérite d'un autre mais remplace une méthode héritée par sa propre implémentation, vous souhaiterez peut-être appeler la méthode remplacée depuis la nouvelle méthode.
Par exemple, si le type d'objet Truck hérite de Vehicle et remplace la méthode Accelerate, alors Truck.Accelerate souhaitera peut-être appeler Vehicle.Accelerate en un ou plusieurs points. Vous pouvez appeler la méthode remplacée avec le format spécial :
- INHERITED Accelerate
En général, pour appeler une méthode appartenant à l'ancêtre immédiat du type d'objet propriétaire de la méthode actuelle, écrivez INHERITED (sans point). La valeur de Self dans la méthode appelée est la même que dans la méthode appelante.
INHERITED ne peut être utilisé que dans un bloc de déclaration de méthode. Il doit précéder un identificateur de méthode hérité par le type d'objet propriétaire du bloc de déclaration de méthode.
Utiliser l'Object Pascal
Il existe deux manières fondamentales d'écrire des applications orientées objet pour Macintosh : sans ou avec MacApp. Cette section traite des deux approches.
Object Pascal sans MacApp
La prise en charge de la syntaxe Object Pascal est incluse dans le compilateur MPW, vous n'avez donc pas besoin de MacApp pour écrire un programme orienté objet. Cependant, vous devez fournir un support d'exécution en liant votre compilation au fichier ObjLib.o.
Vous devez également accéder à l'unité Objintf.p en incluant la déclaration USES ObjIntf.p dans votre source texte. ObjIntf.p fournit l'interface pour le type TObject, n'ayant pas de champs de données et quatre méthodes : ShallowClone, Clone, ShallowFree et Free. Ces routines sont décrites ci-dessous.
Les routines Object Pascal
Cette section décrit cinq fonctions disponibles pour une utilisation en programmation orientée objet sans MacApp.
La fonction Member
Member(anObject, aType) |
Member est utilisé uniquement dans la programmation orientée objet. Le membre teste si un objet particulier est d'un type d'objet particulier ou un descendant de ce type. Il possède deux paramètres :
- AnObject est une référence d'objet. C'est une erreur si un objet n'est pas défini. AnObject peut avoir la valeur NIL.
- AType est un type d'objet.
Member renvoie true si anObject n'est pas NIL et si l'objet auquel il fait référence est un membre du type aType. Le paramètre anObject est membre d'aType s'il est de ce type ou descendant d'aType.
Bien que rarement utilisé, Member est utile pour filtrer les coercitions de référence d'objet douteuses. L'utilisation suivante de Member est cependant fortement déconseillée, car elle annule les avantages de la programmation orientée objet :
Au lieu de cela, définissez le code dans chaque clause THEN comme méthode des types correspondants. Donnez à toutes les méthodes le même nom et les mêmes arguments. Assurez-vous que leur ancêtre commun déclare également la méthode. Écrivez un appel de méthode au lieu de l'intégralité de l'instruction conditionnelle. De cette façon, de nouveaux types peuvent être ajoutés sans modifier le programme, ce qui constitue l'un des principaux avantages de la programmation orientée objet.
La fonction ShallowClone
ShallowClone |
ShallowClone renvoie une copie d'un objet de type TObject. C'est une fonction sans aucun paramètre. Vous ne devriez pas l'ignorer. Si vous souhaitez le remplacer pour copier les objets référencés par les champs, utilisez Clone, décrit ci-dessous.
La fonction Clone
Clone |
Clone appelle normalement ShallowClone, mais peut être remplacé pour copier des objets référencés par des champs.
La fonction ShallowFree
ShallowFree |
ShallowFree libère l'espace occupé par un objet en mémoire. Vous ne devez pas le remplacer. Si vous souhaitez le remplacer par des objets libres référencés par des champs, utilisez Free, décrit ci-dessous.
La fonction Free
Free |
Free appelle normalement ShallowFree, mais peut être remplacé par des objets libres référencés par des champs.
Object Pascal avec MacApp
Le moyen le plus simple de créer une application orientée objet consiste à utiliser MacApp, l'application Macintosh extensible d'Apple. MacApp implémente l'interface utilisateur Macintosh dans un environnement orienté objet. Pour créer une application spécifique adaptée à vos besoins, vous développez MacApp en y ajoutant une série de déclarations de types d'objets descendants, dont chacune hérite d'une partie du type d'objet d'origine. Ces objets descendants deviennent les éléments de votre programme.
Dès le départ, chaque nouveau type d'objet hérité de MacApp est garanti de fonctionner car son type d'objet ancêtre a déjà été débogué. Ses données et méthodes forment un tout intégré&nbps;: un ordinateur virtuel au sein de votre programme exécutant un groupe spécifique de tâches. Au fur et à mesure que vous le modifiez pour répondre à vos besoins, vous pouvez tester chaque modification et voir ses effets. Cela vous aide à réaliser un développement de programme ordonné et sans bogue. Le MacApp vous libère de nombreuses tâches requises lorsque vous écrivez un programme à partir de zéro. Il vous permet de vous concentrer sur les parties de votre candidature spécifiques au travail qu'elle effectue.
Le MacApp fournit un support d'exécution intégré pour la programmation orientée objet ; vous n'avez pas besoin de lier votre programme à un autre fichier.