- Le type OBJECT
- Heritage et genetique
- Constructeurs, destructeurs et méthodes virtuelles
- Methodes et propriétés privées
Dans ses premières versions, le Turbo Pascal était un langage dit traditionnel : les données étaient transmises
à un ensemble de fonctions et procédures, lesquelles se chargaient de les controler, et le cas écheant, de les modifier.
Puis au fil du temps, le langage a subit la même évolution que l’on pu observer du C au C++ : les
objets sont apparus.
En programmation orientée objet, ce sont les données qui possédent des procédures et des fonctions. Ces procédures
et fonctions (alors appelées methodes) operent sur l’objet dont elles dépendent.
Cependant, il ne faut pas croire que cela fait de Turbo Pascal un vrai langage orienté objet, comme peut l’être
Java. Il faut plutôt l’entendre dans le même sens que l’orientation objet de C++ (c’est à dire un peu, mais pas
completement).
Le type OBJECT
Comme pour une variable classique, un objet est toujours d’un type bien particulier. Dans le cas particuler
de la programmation orientée objet, on dit alors que le type est une classe, et que la variable est un
objet ou une instance de cette classe.
Pour créer une classe, il faut créer un Type du type OBJECT :
TypeTObjet = OBJECT
{propriétés}
{methodes}
end;
TObjet represente le nom de la classe. Les propriétés sont en fait des champs, comme pour les
types RECORD (voir page précedente).
Imaginons que nous voulions ecrire un programme ayant pour but de gerer un stock de velo. Chaque velo poura
être caractèrisé par plusieurs propriétés comme : la marque, le modéle, la taille, le prix.
On peut alors créer un type TVelo tel que celui-ci :
TypeTVelo = OBJECT
Marque : String;
Modele : String;
Taille : Byte;
Prix : Integer;
end;
Jusqu’ici, rien de nouveau par rapport au type RECORD.
Mais ce n’est pas tout : cette classe TVelo va aussi posseder un ensemble de méthodes. Nous allons
tous d’abord créer la méthode Bapteme qui va permetre d’initaliser les propriétés Marque, Modele,
Taille et Prix.
Pour ajouter une méthode à une classe, on déclare son prototype (c’est à dire la premiere ligne du bloc)
juste après la déclaration de ses propriétés :
TypeTVelo = OBJECT
Marque : String;
Modele : String;
Taille : Byte;
Prix : Integer;
procedure Bapteme(aMarque, aModele : String;
aTaille : Byte; aPrix : Integer);
end;
Ensuite, on va créer une procédure TVelo.Bapteme où l’on écrira le code. Les propriétés de l’objet
sont directement accessibles. On peut cependant utiliser le parametre self qui permet de faire reference
à l’objet (par exemple, ecrire self.Marque au lieu de Marque).
procedure TVelo.Bapteme(aMarque, aModele : String; aTaille : Byte; aPrix : Integer);
begin
Marque := aMarque;{Affectation des arguments aux propriétés}
Modele := aModele;
Taille := aTaille;
Prix := aPrix;
end;
Et voila, le tour est joué! Il ne reste plus qu’a créer une variable du type TVelo, et d’utiliser ces methodes
et propriétés.
Pour appeler une méthode d’un objet, il suffit d’écrire son nom à la suite de l’objet, en ayant bien pris soin
d’interposer l’opérateur . (le point).
Voici le code complet :
TypeTVelo = OBJECT
Marque : String;
Modele : String;
Taille : Byte;
Prix : Integer;
procedure Bapteme(aMarque, aModele : String;
aTaille : Byte; aPrix : Integer);
end;
procedure TVelo.Bapteme(aMarque, aModele : String; aTaille : Byte; aPrix : Integer);
begin
Marque := aMarque;{Affectation des arguments aux propriétés}
Modele := aModele;
Taille := aTaille;
Prix := aPrix;
end;
var MonVelo : TVelo;{Une instance de la classe TVelo}
begin
MonVelo.Bapteme('SUNN', 'Asphalt +', 52, 6500);{Appel de la methode
Bapteme}
writeln('Marque : ', MonVelo.Marque);{Affichage des}
writeln('Modele : ', MonVelo.Modele);{propriétés}
writeln('Taille : ', MonVelo.Taille);
writeln('Prix : ', MonVelo.Prix);
end.
Les méthodes d’un objets ne sont pas limitées au seules procédures. Il est egalement possible qu’une méthode
puisse renvoyer une valeur : il y a juste à déclarer une fonction (function).
Voici une nouvelle méthode, ainsi que la nouvelle déclaration du type TVelo :
TypeTVelo = OBJECT
Marque : String;
Modele : String;
Taille : Byte;
Prix : Integer;
procedure Bapteme(aMarque, aModele : String;
aTaille : Byte; aPrix : Integer);
function EstAlaTaille(TEJ : Real) : Boolean;
end;
function TVelo.EstAlaTaille(TEJ : Real) : Boolean;
begin
{Cette methode renvoi TRUE si le velo est à la taille (avec
toutes fois une certaine tolérance) suivant la valeur de TEJ
qui represente la taille de l'entrejambe}
If (Taille > TEJ * 0.60) And (Taille < TEJ * 0.70) Then
EstAlaTaille := true
Else
EstAlaTaille := false;
end;
Heritage et genetique
Là où les objets deviennent encore plus interessants, c'est lorsqu'on commence à créer des objets "hybrides".
Créons par exemple une classe THomme, telle que celle ci :
Type THomme = OBJECT
Nom : String;
Prenom : String;
Age : Byte;
procedure Bapteme(aNom : String; aPrenom : String;
aAge : Byte);
end;
procedure THomme.Bapteme(aNom : String; aPrenom : String; aAge : Byte);
begin
Nom := aNom;
Prenom := aPrenom;
Age := aAge;
end;
Maintenant, nous allons créer une autre classe qui descendra de cette même classe THomme. Par exemple,
une classe TPompier. Cette classe va heriter de toutes les propriétés et methodes de son ancetre
(dans notre cas Nom, Prenom, Age et Bapteme).
Pour réaliser cette manipulation genetique, il suffit de passer le nom de la classe parent (THomme)
entre parenthèses, après le mot-clef OBJECT de la déclaration de la classe enfant (TPompier) :
Type THomme = OBJECT
Nom : String;
Prenom : String;
Age : Byte;
procedure Bapteme(aNom : String; aPrenom : String;
aAge : Byte);
end;
TPompier = OBJECT(THomme){Classe descendante de THomme}
(* Pour l'instant, aucune propriété *)
(* ni méthodes *)
end;
procedure THomme.Bapteme(aNom : String; aPrenom : String; aAge : Byte);
begin
Nom := aNom;
Prenom := aPrenom;
Age := aAge;
end;
var SoldatDuFeu : TPompier;
begin
With SoldatDuFeu Do
begin
Bapteme('FONEBONE', 'Stephano', 25);
writeln('Nom : ', Nom);
writeln('Prenom : ', Prenom);
writeln('Age : ', Age);
end;
end.
Comme prevu, toutes les propriétés et méthodes énoncées plus haut sont belles et biens disponibles.
Mais ce n'est pas tout : vu qu'un pompier est (beaucoup
) plus qu'un homme, nous allons un petit peu le personaliser.
Ajoutons, pour commencer, la propriété Grade :
TPompier = OBJECT(THomme)
Grade : String;
d;
Cette propriété Grade ne sera accessible qu'avec les objets issus de la classe TPompier, et
non de la classe parente THomme.
Mais voila que maintenant, nous aimerions pouvoir initaliser cette même propriété lors de l'appel de la méthode
Bapteme. Or, cette méthode, telle qu'elle est en ce moment, ne permet pas une telle action.
Nous allons donc la redefinir dans l'objet TPompier :
Type THomme = OBJECT
Nom : String;
Prenom : String;
Age : Byte;
procedure Bapteme(aNom : String; aPrenom : String;
aAge : Byte);
end;
TPompier = OBJECT(THomme)
Grade : String;
procedure Bapteme(aNom : String; aPrenom : String;
aAge : Byte; aGrade : String);
end;
procedure THomme.Bapteme(aNom : String; aPrenom : String; aAge : Byte);
begin
Nom := aNom;
Prenom := aPrenom;
Age := aAge;
end;
procedure TPompier.Bapteme(aNom : String; aPrenom : String;
aAge : Byte; aGrade : String);
begin
inherited Bapteme(aNom, aPrenom, aAge);
Grade := aGrade;
end;
var SoldatDuFeu : TPompier;
begin
With SoldatDuFeu Do
begin
Bapteme('FONEBONE', 'Stephano', 25, 'Sergent Chef');
writeln('Nom : ', Nom);
writeln('Prenom : ', Prenom);
writeln('Age : ', Age);
writeln('Grade : ', Grade);
end;
end.
Et voici que nous faisons intervenir un element nouveau : inherited. inherited permet
d'appeler une méthode appartenant à l'objet parent. Ici, nous appelons la méthode Bapteme de l'objet
THomme, qui nous permet d'initialiser les trois premières propriétés. Ensuite, nous assignons
la valeur transmise à la propriété Grade. Ainsi, le travail devient plus facile.
Constructeurs, destructeurs et méthodes virtuelles
Comme pour les variables, on peut affecter la valeur d'un objet à un autre objet.
Prenons un exemple :
typeTPapa = OBJECT
Place : String;
procedure QuiSuisJe;
end;
TFils = OBJECT(TPapa)
Prop : String;
procedure QuiSuisJe;
end;
procedure TPapa.QuiSuisJe;
begin
writeln('Je suis le papa');
end;
procedure TFils.QuiSuisJe;
begin
writeln('Je suis le fils');
end;
varPapa : TPapa;
Fils : TFils;
begin
Papa.Place := 'Papa';
Fils.Place := 'Fils';
Fils.Prop := 'Propriété spécifique à Fils';
writeln(Papa.Place);{Sortie -> Papa}
Papa.QuiSuisJe;{Sortie -> Je suis le papa}
Papa := Fils;{Affectation de Fils à Papa}
writeln(Papa.Place);{Sortie -> Fils}
Papa.QuiSuisJe;{Sortie -> Je suis le papa}
end.
Comme on peut le remarquer dans cet exemple, seules les propriétés d'un objet sont affectées dans ce type d'opération. Les méthodes ne sont pas
"échangées", comme le prouve la ligne Papa.QuiSuisJe qui est donc toujours une méthode de la classe TPapa, et non de la
classe TFils (sinon, nous aurions eu comme résultat : Je suis le fils).
Vous remarquez que nous avons affecté l'objet descendant (Fils) à l'objet ascendant (Papa). L'inverse est impossible :
seules les classes descendantes peuvent être affectées à des classes parentes (pléonasme volontaire, afin de bien souligner la chose
).
De plus, les precisions apportées à la classe heritière (ici, le champ Prop de la classe TFils) ne sont pas transmises.
Ainsi, si le code suivant provoque une erreur :
writeln(Papa.Prop);{ERREUR : Field Indentifer Excepted}
Mais alors comment faire si on veut pouvoir échanger les méthodes de deux objets lors d'une telle opération ?
Il faut utiliser des objets dynamiques, et des méthodes virtuelles. Pour cela, nous allons retrouver les pointeurs, que nous avons
déjà abordé dans des sections antérieures.
Nous allons tout d'abord rajouter le mot-clef virtual derrière la déclaration des procédures dans les classes, afin de rendre la méthode
QuiSuisJe virtuelle.
Une méthode virtuelle n'est liée à un objet qu'au moment de l'execution, et non lors de la compilation. On dit alors que la ligature est dynamique,
car non figée une fois pour toutes à un seul et unique objet. Et c'est precisement l'effet que nous recherchons dans notre cas.
Ensuite, nous allons devoir déclarer un constructeur. Un constructeur est tout simplement une méthode qui doit être appelée avant d'utiliser
l'objet auquel elle appartient. Même si cette méthode ne produit rien de particulier, elle doit tout de même exister.
Dans notre cas, elle initalisera la propriété Place avec Fils ou Papa dans le cas approprié.
Un constructeur se déclare avec le mot-clef constructor devant son identificateur. Il peut prendre des arguments si besoin est.
Puis enfin, pour créer un objet dynamiquement, il faut déclarer un pointeur vers cet objet, et utiliser la procedure New. New demande
2 arguments : le premier est le pointeur vers l'objet, et le deuxième est la méthode constructeur à appeler.
Notre exemple devient alors devient alors :
typeTPapa = OBJECT
Place : String;
constructor Init;
procedure QuiSuisJe; virtual;
end;
TFils = OBJECT(TPapa)
Prop : String;
constructor Init;
procedure QuiSuisJe; virtual;
end;
constructor TPapa.Init;
begin
Place := 'Papa';
end;
procedure TPapa.QuiSuisJe;
begin
writeln('Je suis le papa');
end;
constructor TFils.Init;
begin
Place := 'Fils';
Prop := 'Propriété spécifique à fils';
end;
procedure TFils.QuiSuisJe;
begin
writeln('Je suis le fils');
end;
varPapa : ^TPapa;
Fils : ^TFils;
begin
New(Papa, Init);
New(Fils, Init);
writeln(Papa^.Place);{Sortie -> Papa}
Papa^.QuiSuisJe;{Sortie -> Je suis le papa}
Papa := Fils;
writeln(Papa^.Place);{Sortie -> Fils}
Papa^.QuiSuisJe;{Sortie -> Je suis le fils}
Dispose(Papa);{Voir plus loin pour Dispose}
Dispose(Fils);{Idem}
end.
Et cette fois-ci, ça marche! Les méthodes ont bien été "échangées".
À l'opposé des constucteurs, on trouve les destructeurs. Les destructeurs sont chargés de détruire l'objet et de libérer la mémoire utilisée
par cet objet.
Les destructeurs sont déclarés à l'aide du mot-clef destructor. Ils doivent être appellés par la procédure Dispose qui possede une
syntaxe semblable à celle de New, à savoir le pointeur vers l'objet et la méthode destructrice.
Cependant, si la méthode ne possede pas de destructeurs, il est possible d'utiliser Dispose sans le deuxième argument, comme ce que
nous avons fait plus haut.
Methodes et propriétés privées
Au sein d'un module (c'est à dire au sein d'une unité ou d'un programme), il est possible de définir des méthodes et/ou des propriétés privées
.
Les méthodes et propriétés privées ne sont accessibles que par le module dans lequel la classe de l'objet est définie.
Ainsi, si on déclare une méthode privée MLambda dans une classe TLambda se trouvant dans l'unité ULambda, et
qu'on décide d'utiliser cette unité ULambda dans un programme PLambda, la méthode MLambda sera alors inaccessible.
Pour déclarer des élements privés, c'est simple : il suffit d'utiliser le mot-clef Private dans la déclaration de la classe.
Tout ce qui sera déclaré en dessous sera alos considéré comme privé :
TypeTLambda = OBJECT
Publique : String;{Elements dits publiques}
procedure PPublique;
PRIVATE
Prive : String;
procedure MLambda;{Elements privés}
end;