Le langage Object Pascal : classes et objets
La plupart des langages de programmation modernes prennent en charge la programmation orientée objet (POO), les langages POO sont basés sur trois concepts fondamentaux : l'encapsulation (généralement mise en oeuvre avec des classes), l'héritage et le polymorophisme (ou liaison tardive).
Vous pouvez écrire des applications Delphi même sans connaître les détails de l'Object Pascal. Lors de la création d'un nouveau formulaire, de l'ajout de nouveaux composantes et de la gestion des événements, le Delphi prépare automatiquement la plupart du code associé. Mais connaître les détails du langage de programmation et sa mise en oeuvre vous aidera à comprendre précisément ce que fait Delphi et à maîtriser complètement le langage de programmation.
Le langage Pascal
Le langage de programmation Object Pascal utilisé par Delphi est une extension POO du langage Pascal classique, que Borland a poussé en avant pendant de nombreuses années avec ses compilateurs Turbo Pascal. La syntaxe du langage de programmation Pascal est connue pour être assez verbeuse et plus lisible que, par exemple, le langage C. Son extension POO suit la même approche, offrant la même puissance que la génération récente de langages de programmation POO, de Java, GO et C#.
Les nouvelles fonctionnalités de Pascal incluent les directives $IF et $ELSEIF pour la compilation conditionnelle, les directives $WARN et $MESSAGE, ainsi que les directives plateforme, bibliothèque et d'allusions obsolètes. Les modifications apportées à l'assembleur (avec de nouvelles directives, la prise en charge des instructions MMX et Pentium Pro, et bien d'autres fonctionnalités) permettent encore plus d'optimiser le code.
D'autres changements relativement mineurs dans le langage incluent un changement de la valeur par défaut du commutateur de compilateur $WRITEABLECONST, étant maintenant désactivé. Cette option permet aux programmes de modifier la valeur des constantes typées et doit généralement être laissée désactivée, en utilisant des variables au lieu de constantes pour les valeurs modifiables. Un autre changement est la prise en charge du type de données Int64 dans les variantes. Enfin, vous pouvez attribuer des valeurs spécifiques aux éléments d'une énumération (comme dans le langage de programmation C/C++), au lieu d'utiliser la séquence de valeurs par défaut.
Les directive de compilateur $IF, $IFDEF,...
Le langage de programmation Delphi a toujours eu une directive $IFDEF que vous pouvez utiliser pour tester si un symbole spécifique a été défini. Le langage de programmation Delphi propose également une directive $IFNDEF, avec le test opposé. Il est utilisé pour obtenir une compilation conditionnelle, comme dans l'exemple suivant :
- {$IFDEF DEBUG}
- { s'exécute uniquement si la directive DEBUG est définie }
- ShowMessage('Code d''execution critique');
- {$ENDIF}
En définissant ou non la directive DEBUG et en recompilant, la ligne de code supplémentaire sera incluse ou ignorée par le compilateur. Cette directive de code est puissante, mais la vérification de plusieurs versions de Delphi et de systèmes d'exploitation peut vous forcer à utiliser plusieurs directives $IFDEF imbriquées, rendant le code totalement illisible. Pour cette raison, le Delphi avait introduit une directive plus puissante pour la compilation conditionnelle, $IF. Dans la directive, vous pouvez utiliser la fonction Defined pour vérifier si un symbole conditionnel est défini ou utiliser la fonction Declared pour voir si une constante de langage de programmation est définie et utiliser ces constantes dans une expression booléenne constante. Voici un code montrant comment utiliser une constante dans la directive $IF :
- Const DebugControl=2;
- {$IF Defined(DEBUG) and (DebugControl > 3)}
- ShowMessage ('Code d''execution critique');
- {$IFEND}
Notez que l'instruction est fermée par un $IFEND et que vous pouvez également avoir une branche $ELSE facultative. Vous pouvez également concaténer des conditions avec la directive $ELSEIF, suivie d'une autre condition et évaluée uniquement comme une alternative à la directive $IF à laquelle elle fait référence :
- {$IF un}
- { ... }
- {$ELSEIF deux}
- { ... }
- {$ELSE}
- { ... }
- {$IFEND}
Dans les expressions de la directive $IF, vous ne pouvez utiliser que des constantes non typées, étant réellement et invariablement traitées comme des constantes par le compilateur. Vous pouvez suivre les règles générales des expressions constantes Pascal. Vous pouvez utiliser tous les opérateurs de langage de programmation, les opérateurs booléens AND, OR, XOR et NOT, et les opérateurs mathématiques, y compris DIV, MOD, +, -, *, /, < et >, pour n'en citer que quelques-uns. Vous pouvez également utiliser des fonctions prédéfinies telles que SizeOf, High, Low, Pred, Succ et autres répertoriés dans les expressions constantes de Delphi. L'expression peut utiliser des symboles constants de n'importe quel type, y compris des nombres réels à virgules flottants et des chaînes de caractères, à condition que l'expression elle-même soit finalement évaluée à une valeur True ou False.
Dans ces expressions constantes, il n'est pas possible d'utiliser des constantes de type, pouvant être éventuellement modifiées dans le code en fonction de l'état de la directive des constantes de type inscriptible ($J ou $WRITEABLECONST). Dans tous les cas, utiliser des constantes que vous pouvez modifier est une très mauvaise idée. Le Delphi fournit quelques symboles conditionnels prédéfinis, y compris la version du compilateur, le système d'exploitation, l'environnement GUI,.. Voici quelqu'uns des symboles conditionnels les plus communs :
Symbole | Description |
---|---|
BCB | Ce symbole est définit lorsque l'IDE de C++ Builder appelle le compilateur Pascal. |
ConditionalExpressions | Ce symbole permet d'indiquer que la directive $IF est disponible. Il est défini dans Kylix et à partir de Delphi 6, mais pas dans les versions antérieures. |
CONSOLE | Ce symbole permet de compiler une application console et non une interface graphique. Ce symbole n'a de sens que sous Windows, car toutes les applications Linux sont des applications console. |
LINUX | Ce symbole permet d'indiquer une compilation sur la plate-forme Linux. Sur Kylix, il existe également les symboles prédéfinis LINUX32, POSIX et ELF. |
WIN32 | Ce symbole permet d'indiquer une compilation uniquement sur la plate-forme Windows en 32 bits. Ce symbole a été introduit dans Delphi 2 pour se distinguer des compilations Windows en 16 bits (Delphi 1 a défini le symbole WINDOWS). Vous devez utiliser WIN32 uniquement pour marquer le code spécifiquement pour Win32, pas pour Win16 ou pour plates-formes Win64. Utilisez plutôt MSWINDOWS pour faire la distinction entre Windows et les autres systèmes d'exploitation. |
MSWINDOWS | Ce symbole permet d'indiquer une compilation sur la plateforme Windows (à partir de Delphi 6). |
VER120 | Ce symbole permet d'indiquer la compilation avec Delphi 4. |
VER130 | Ce symbole permet d'indiquer la compilation avec Delphi 5. |
VER140 | Ce symbole permet d'indiquer la compilation avec Delphi 6, étant la version 14.0 du compilateur Borland Pascal. |
Vous pouvez également utiliser la constante RTLVersion définie dans l'unité System pour tester la version de Delphi (et sa bibliothèque d'exécution) sur laquelle vous compilez. Le symbole prédéfini ConditionalExpressions peut être utilisé pour protéger les nouvelles directives des anciennes versions de Delphi :
- {$IFDEF ConditionalExpressions}
- {$IF System.RTLVersion > 14.0}
- { Mettre quelquechose ... }
- {$IFEND}
- {$ENDIF}
Il est recommandé d'utiliser la compilation conditionnelle avec parcimonie et uniquement lorsqu'elle est vraiment nécessaire. Il est généralement préférable, dans la mesure du possible, d'écrire du code pouvant s'adapter à différentes situations - par exemple, ajouter différentes versions de la même classe (ou différentes classes héritées) au même programme. Une utilisation excessive de la compilation conditionnelle rend un programme difficile à lire et à déboguer.
N'oubliez pas d'émettre une commande Build All lorsque vous modifiez un symbole conditionnel ou une constante, ce qui peut affecter une compilation conditionnelle; sinon, les unités affectées ne seront pas recompilées à moins que leur code source ne change.
Les directives obsolètes
La prise en charge de plusieurs systèmes d'exploitation dans la même base de code source implique un certain nombre de problèmes de compatibilité. Outre une bibliothèque d'exécution modifiée et une toute nouvelle bibliothèque de composantes, le Delphi inclut des directives spéciales pour marquer des parties spéciales de le code. En introduisant l'idée d'avertissements et de messages personnalisés, ils en ont ajouté quelques-uns spéciaux prédéfinis.
La directive platform
La première directive de ce groupe est la directive platform, utilisée pour marquer le code non portable. Cette directive peut être utilisée pour marquer des procédures, des variables, des types et presque tous les symboles définis. Le Delphi utilise la plate-forme dans ses bibliothèques, de sorte que lorsque vous utilisez une fonctionnalité spécifique à la plate-forme (par exemple, en appelant la fonction IncludeTrailingBackslash de l'unité SysUtils), vous recevrez un message d'avertissement, tel que :
Symbol 'IncludeTrailingBackslash' is specific to a platform. |
Cet avertissement est un indice pour les développeurs envisageant de porter leur code entre les plates-formes Linux et Windows. Dans de nombreux cas, vous pourrez trouver une approche alternative entièrement indépendante de la plate-forme. Dans le cas de la fonction IncludeTrailingBackslash, il existe une version, appelée IncludeTrailingDelimiter, étant également portable sur un système de fichiers basé sur Unix. Bien sûr, vous pouvez utiliser la directive platform pour marquer votre code, par exemple, si vous écrivez une composante ou une bibliothèque ayant des fonctionnalités spécifiques à la plate-forme. Voici quelques exemples :
La position des points-virgules pour les directives d'indication peut être assez déroutante au début. La règle est qu'une directive d'indication doit apparaître avant le point-virgule après le symbole qu'elle modifie. Mais une procédure, une fonction ou une déclaration d'entête d'unité ne peut être suivie que de mots réservés, de sorte que sa directive d'indication peut apparaître après le point-virgule. Une déclaration de type, variable ou constante peut être suivie d'un autre identificateur, la directive obsolète doit donc précéder le point-virgule fermant sa déclaration. Une partie de la justification derrière cela est que les directives d'indication ne sont pas des mots réservés, elles peuvent donc être utilisées comme nom d'un identificateur.
La directive deprecated
La directive deprecated fonctionne de la même manière que la directive platform; les seules vraies différences sont qu'il est utilisé dans un contexte différent et produit un avertissement différent du compilateur. Le rôle de deprecated est de marquer les identificateurs faisant toujours partie du système pour des raisons de compatibilité, mais allant être supprimés à l'avenir ou vous exposer à des risques d'incompatibilité. Ce symbole est utilisé avec parcimonie dans la bibliothèque Delphi.
La directive library
La directive library fonctionne de la même manière que deprecated et platform; son rôle est de marquer le code ou les composantes spécifiques à une bibliothèque (VCL ou CLX) et n'étant pas portables parmi eux. Cependant, apparemment, ce symbole n'est jamais utilisé dans la bibliothèque Delphi.
La directive $WARN
La directive $WARNINGS (et l'option correspondante du compilateur) vous permet de désactiver tous les messages d'avertissement. La plupart des programmeurs aiment garder les messages et ont tendance à travailler avec des programmes se compilant sans conseils ni avertissements. Cependant, avec l'avènement des trois directives obsolètes discutées, il existe des programmes spécifiquement destinés à une plate-forme, ne pouvant pas compiler sans avertissements de compatibilité.
Pour surmonter cette situation, à partir de Delphi 6, ils ont introduit la directive $WARN, spécifiquement destinée à désactiver les directives obsolètes. Par exemple, vous allez désactiver les conseils de plate-forme en écrivant ce code :
{$WARN SYMBOL_PLATFORM OFF}
La directive $WARN a cinq paramètres différents, liés aux trois directives obsolètes, et peut utiliser les valeurs ON et OFF pour chacun :
Valeur | Description |
---|---|
SYMBOL_PLATFORM | Cette valeur permet de désactiver la directive platform dans l'unité courante ou dans l'unité où la directive est spécifiée. L'avertissement, en fait, est émis lors de la compilation du code utilisant le symbole, pas lors de la compilation du code avec la définition. |
UNIT_PLATFORM | Cette valeur permet de désactiver la directive platform dans l'unité courante ou dans l'unité où la directive est spécifiée. L'avertissement, en fait, est émis lors de la compilation du code utilisant le symbole, pas lors de la compilation du code avec la définition. |
SYMBOL_LIBRARY | Cette valeur permet de désactiver la directive platform dans la bibliothèque courante ou dans la bibliothèque où la directive est spécifiée. L'avertissement, en fait, est émis lors de la compilation du code utilisant le symbole, pas lors de la compilation du code avec la définition. |
UNIT_LIBRARY | Cette valeur permet de désactiver la directive platform dans la bibliothèque courante ou dans la bibliothèque où la directive est spécifiée. L'avertissement, en fait, est émis lors de la compilation du code utilisant le symbole, pas lors de la compilation du code avec la définition. |
SYMBOL_DEPRECATED | Cette valeur permet de désactiver la directive deprecated. |
La directive Message
Le compilateur a la capacité de générer des avertissements dans de nombreuses situations différentes, de sorte que le développeur d'une bibliothèque ou d'une partie d'un programme puisse informer les autres programmeurs d'un problème ou d'un risque donné lors de l'utilisation d'une fonctionnalité donnée, lorsque le programme peut encore légalement compiler. Une extension de cette idée est de permettre aux programmeurs d'insérer des messages d'avertissement personnalisés dans le code, avec cette syntaxe :
{$MESSAGE 'Ancienne version de l''unité: pensez à utiliser la version mise à jour' } |
La compilation de ce code émettra un message d'indication avec le texte fourni. Cette fonction peut être utilisée pour indiquer les problèmes possibles, suggérer des approches alternatives, marquer le code non terminé,... C'est probablement plus fiable que d'utiliser un élément TODO, car un programmeur peut ne pas ouvrir la fenêtre To-Do List mais le compilateur lui rappellera le problème en suspens. Cependant, c'est le compilateur émettant le message, vous le verrez donc même si la partie donnée du code n'est pas vraiment utilisée par le programme car l'éditeur de liens la supprimera du fichier exécutable. Ces types de messages sans attaches, comme les directives obsolètes, deviennent très utiles pour permettre au développeur d'une composante de communiquer avec les programmeurs l'utilisant, en avertissant des pièges potentiels.
Présentation des classes et des objets
La pierre angulaire des extensions POO disponibles dans Object Pascal est représentée par le mot-clef class, étant utilisé dans les déclarations de type. Les classes définissent le plan des objets que vous créez dans Delphi. Comme les termes classe et objet sont couramment utilisés et souvent mal utilisés, soyons sûrs d'être d'accord sur leurs définitions.
Une classe est un type de données défini par l'utilisateur, ayant un état (sa représentation) et certaines opérations (son comportement). Une classe a des données internes et des méthodes, sous la forme de procédures ou de fonctions, et décrit généralement les caractéristiques génériques et le comportement de certains objets similaires.
Un objet est une instance d'une classe ou une variable du type de données défini par la classe. Les objets sont des entités réelles. Lorsque le programme s'exécute, les objets prennent de la mémoire pour leur représentation interne. La relation entre objet et classe est la même que celle entre variable et type.
Pour déclarer un nouveau type de données de classe dans Object Pascal, avec certains champs de données locaux et certaines méthodes, utilisez la syntaxe suivante :
La convention dans Delphi est d'utiliser la lettre T comme préfixe pour le nom de chaque classe que vous écrivez et pour tous les autres types (T est un type). C'est juste une convention - pour le compilateur, T n'est qu'une lettre comme les autres - mais c'est si courant que le suivre rendra votre code plus facile à comprendre.
Ce qui suit est une définition de classe complète, avec deux méthodes déclarées et pas encore complètement définies. La définition de ces deux méthodes (la fonction LeapYear et la procédure SetValue) doit être présente dans la même unité de la déclaration de classe et est écrite avec cette syntaxe :
Les noms de méthodes sont préfixés avec le nom de classe (en utilisant la notation par points), car une unité peut contenir plusieurs classes, éventuellement avec des méthodes ayant les mêmes noms. Vous pouvez en fait éviter de retaper les noms de méthode et la liste de paramètres en utilisant la fonctionnalité de complétion de classe de l'éditeur. Tapez ou modifiez simplement la définition de classe et appuyez sur Ctrl+Shift+C pendant que le curseur se trouve dans la définition de classe elle-même; cela permettra à Delphi de générer un squelette de la définition des méthodes, y compris les instructions Begin et End. Une fois la classe définie, nous pouvons créer un objet et l'utiliser comme suit :
Notez que ADay.LeapYear est une expression similaire à ADay.Year, bien que le premier soit un appel de fonction et le second un accès direct aux données. Vous pouvez éventuellement ajouter des parenthèses après l'appel d'une fonction sans paramètre.
Classes, objets et programmation visuelle
Lorsque vous créez simplement une nouvelle application avec un formulaire et placez un bouton dessus pour exécuter du code lorsque vous appuyez sur le bouton, vous créez une application orientée objet. En fait, le formulaire est un objet d'une nouvelle classe (par défaut TForm1, héritant de la classe TForm de base fournie par Delphi), et le bouton est une instance de la classe TButton, fournie par Delphi, comme vous pouvez le voir dans le extrait de code suivant :
Le mot-clef Self
Les méthodes sont très similaires aux procédures et aux fonctions. La vraie différence est que les méthodes ont un paramètre implicite, étant une référence à l'objet actuel. Dans une méthode, vous pouvez faire référence à ce paramètre (l'objet courant) à l'aide du mot-clef Self. Ce paramètre caché supplémentaire est nécessaire lorsque vous créez plusieurs objets de la même classe, de sorte que chaque fois que vous appliquez une méthode à l'un des objets, la méthode fonctionnera uniquement sur ses propres données et n'affectera pas les objets frères.
Par exemple, dans la méthode SetValue de la classe TDate, répertoriée précédemment, nous utilisons simplement Month, Year et Day pour faire référence aux champs de l'objet actuel, ce que vous pourriez exprimer comme :
C'est en fait ainsi que le compilateur Delphi convertie le code, pas comment vous êtes censé l'écrire. Le mot-clef Self est une construction de langage fondamentale utilisée par le compilateur, mais parfois il est utilisé par les programmeurs pour résoudre les conflits de nom et rendre le code délicat plus lisible.
Les langages de programmation C++ et Java ont une fonctionnalité similaire basée sur les mot-clef this pour C++ et this pour Java.
Tout ce que vous devez vraiment savoir sur Self, c'est que la mise en oeuvre technique d'un appel à une méthode diffère de celle d'un appel à un sous-programme générique. Les méthodes ont un paramètre caché supplémentaire, Self.
Si vous regardez la définition du type de données TMethod dans l'unité System, vous verrez qu'il s'agit d'un enregistrement avec un champ Code et un champ Data. Le premier est un pointeur vers l'adresse de la fonction en mémoire; le second la valeur du paramètre Self à utiliser lors de l'appel de cette adresse de fonction.
Méthodes de surcharges
L'Object Pascal prend en charge les fonctions et méthodes de surcharges : vous pouvez avoir plusieurs méthodes avec le même nom, à condition que les paramètres soient différents. En vérifiant les paramètres, le compilateur peut déterminer laquelle des versions de la routine que vous souhaitez appeler.
Il existe deux règles de base : chaque version de la méthode doit être suivie du mot-clef overload et les différences doivent être dans le nombre ou le type des paramètres ou les deux. Le type de retour ne peut pas être utilisé pour distinguer deux méthodes.
La surcharge peut être appliquée aux fonctions et procédures globales et aux méthodes d'une classe. À titre d'exemple de surcharge, la même classe TDate à deux versions différentes de la méthode SetValue :
- Type
- TDate=Class
- Public
- Month,Day,Year:Integer;
- Procedure SetValue(annee,mois,jour:Integer); Overload;
- Procedure SetValue(NewDate:TDateTime); Overload;
- Function LeapYear:Boolean;
- End;
-
- Procedure TDate.SetValue(annee,mois,jour:Integer);Begin
- Month:=mois; Day:=jour; Year:=annee;
- fDate:=EncodeDate(annee,mois,jour);
- End;
-
- Procedure TDate.SetValue(NewDate:TDateTime);Begin
- fDate:=NewDate;
- End;
-
- Function TDate.LeapYear:Boolean;Begin
- Result:=IsLeapYear(Year);
- End;
Dans Delphi, le compilateur a été amélioré pour améliorer la résolution des méthodes de surcharges, permettant la compilation d'appels considérés comme ambigus. En particulier, le compilateur gère la différence entre les types AnsiString et WideString. La résolution de surcharge a également un meilleur support pour les paramètres de type de données Variant (fournissant des correspondances au cas où il n'y aurait pas de correspondance exacte pour une autre version surchargée) et les interfaces (ayant la priorité sur les types d'objets). Enfin, le compilateur permet à la valeur NIL de correspondre à un paramètre de type d'interface. Certaines de ces améliorations sont aussi dans le compilateur Kylix.