Developpez.com - Rubrique Lazarus

Le Club des Développeurs et IT Pro

Notions fondamentales de Programmation Orientée Objet avec Free Pascal/Lazarus

Un tutoriel de Gilles Vasseur

Le 2016-06-19 21:15:47, par Alcatîz, Responsable Pascal, Lazarus et Assembleur
POO à gogo !
Notions fondamentales de Programmation Orientée Objet avec Free Pascal/Lazarus

Faites-vous partie de ceux qui ne s'aventurent guère au-delà du noyau historique de Pascal ? Pourtant toutes les applications graphiques et tous les composants produits avec Lazarus sont tributaires de la Programmation Orientée Objet. Plus encore : bien qu'il ne se veuille pas contraignant, le compilateur Free Pascal qui sous-tend cet environnement a été entièrement pensé pour manipuler des objets à travers le concept de classe. Alors, lancez-vous ! Avec ce tutoriel, vous oserez enfin aborder les notions fondamentales relatives à la Programmation Orientée Objet avec Free Pascal.

http://gilles-vasseur.developpez.com...rientee-objet/

Et vous ?
Que pensez-vous de ce tutoriel ?
Avez-vous l'habitude de "penser POO" lorsque vous développez vos applications ?
Ou ce tutoriel vous incitera-t-il à le faire ?

Retrouver les meilleurs cours et tutoriels pour apprendre Lazarus
Retrouver les meilleurs cours et tutoriels pour apprendre la programmation
  Discussion forum
11 commentaires
  • gvasseur58
    Responsable Lazarus & Pascal
    Bonjour,

    Le tutoriel a été mis à jour :

    • correction de l'erreur signalée par Jipété ;
    • intégration des notes de bas de page ;
    • présentation améliorée (avec couleurs conservées) ;
    • reformulations et compléments de deux passages signalés comme équivoques ou incomplets ;
    • le lien vers les exemples est disponible (et correct !).


    Des exemples supplémentaires sont en cours d'élaboration, car rien ne vaut la pratique !

    Cordialement,

    Gilles
  • gvasseur58
    Responsable Lazarus & Pascal
    Salut Jipété !

    Tout d'abord, merci pour ta lecture attentive dont je vais tirer le meilleur afin d'améliorer ce tutoriel. Je ne manquerai pas de te citer dans les personnes qui ont collaboré à ce texte.

    Envoyé par Jipété

    Page 4 au milieu, on découvre la déclaration de classe ; tout le monde en a déjà vu, mais il aurait été bon, dans un tuto partant de la base, d'expliquer les mots-clé qu'on y rencontre (private, public, etc.), surtout quand il y a des choses jamais vues par ailleurs, je pense à l'ajout du mot-clé "strict" : c'est quoi la différence entre "private" et "strict private" ? C'est encore plus private que private ?
    Il est indiqué en note que la structure à ce niveau n'a pas à être maîtrisée : l'explication partielle vient un tout petit peu plus tard et la notion de portée (y compris strict private) est expliquée page 16, lorsqu'elle devient vraiment nécessaire. Je pouvais aussi montrer en premier lieu une classe mal ficelée et partielle, mais il m'a semblé plus honnête d'en montrer une "réelle" si l'on veut comparer. Plus privé que strict private, tu meurs !

    Envoyé par Jipété

    Typo page 5 : {$mode objfp} --> {$mode objfpc}
    Je vais corriger...

    Envoyé par Jipété

    Page 7 : "préfixée du nom de l'objet qui la convoque, suivi d'un point" ? --> "préfixée du nom de l'objet qui l'invoque, suivie d'un point"
    Ben non ! C'est bien l'objet qui est suivi d'un point et non la méthode. La construction est correcte (dixit l'éminent correcteur) : je vais cependant reformuler le tout pour le rendre plus clair.

    Envoyé par Jipété

    Il est curieux que la page intitulée "Notes Bas de Page" présente ces fameuses notes toutes regroupées en fin de document, ce qui ne facilite pas la lecture.
    Je suis bien d'accord, mais je n'ai pas de prise sur ce phénomène. Je vais par conséquent essayer de supprimer toutes les notes pour les intégrer au texte. [En fait, il faut que j'apprenne à mieux me servir des outils internes :weird:]

    Envoyé par Jipété

    Et le truc le plus perturbant, pour moi, est l'absence d'espace, dans 99% des cas, entre le mot-clé en couleur et le mot qui suit. Un exemple parmi tant d'autres, à la 3e ligne de la page 7 :
    Encore bien d'accord. Cette absence d'espace n'apparaît qu'à la mise en ligne ! Vais-je devoir supprimer la couleur ou créer des espaces superflus ? Un suspens intenable règne.

    Envoyé par Jipété

    Sinon, c'est bien, c'est très bien ! À quand la suite avec les machins virtuels, abstract, etc. ?
    Bientôt

    Cordialement,

    Gilles

    ***************** Modification *****************
    Le texte a été revu : la coquille a été corrigée ; les couleurs sont bien gérées (ajout d'un espace supplémentaire) ; les notes de bas de page ont été réduites au minimum. 20/06/2016 20h41.
  • gvasseur58
    Responsable Lazarus & Pascal
    Bonjour ThWilliam !

    Comme pour Jipété, merci pour ta lecture attentive et tes remarques qui vont m'aider à améliorer ce tutoriel.

    Envoyé par ThWilliam
    Notamment :

    * l'accès aux propriétés par les "Getters" et "Setters". Dans TAnimal, le setter "SetNom" n'est pas utile, car la procédure ne fait qu'allouer la valeur à FNom. Un setter aurait été utile pour, p.ex., vérifier la valeur assignée en interdisant certaines choses.
    Dans ce tutoriel, j'essaye de donner de bonnes habitudes : le setter n'est pas utile ici tant que les fondements de la classe restent ceux-ci, mais, rassure-toi, un tutoriel qui ne concerne que les propriétés est en cours de rédaction (en fait, il est terminé, mais doit être relu). Le sujet sera largement développé.

    Envoyé par ThWilliam

    * Le tuto parle très peu du constructeur, ce qui me parait être une notion fondamentale. Contrairement à ce qui est dit, j'utilise toujours un constructeur dans lequel j'initialise les différents champs même si ceux-ci gardent leur valeur par défaut. Habitude prise sous Delphi qui conseillait d'ailleurs vivement d'indiquer aussi la valeur par défaut dans la déclaration de propriété (quand c'est autorisé) :
    property ASoif: Boolean read fASoif write fASoif default false;
    N'oublions pas non plus que, dans beaucoup de cas, un champ de type integer (p.ex) doit être initialisé <> 0.
    Ces remarques me plaisent bien. Si tu m'y autorises, je les intégrerais volontiers au texte. Tu seras bien entendu ajouté aux intervenants cités à la fin du tutoriel.

    Pour tout le monde, trois tutoriels dans le même esprit sont en cours de finalisation :
    1. les méthodes sous toutes leurs formes
    2. les propriétés sous toutes leurs formes
    3. les auxiliaires de classe

    Bien sûr, si certains sont intéressés...

    Cordialement,

    Gilles
  • gvasseur58
    Responsable Lazarus & Pascal
    Envoyé par BeanzMaster
    Bonjour, merci pour tes explications complémentaires et ta réactivité Gilles, je comprend mieux l'utilité de strict qui est vraiment utile dans beaucoup de cas, faudra maintenant que je pense à mieux structurer mes classes . Juste une chose :

    Code :
    1
    2
    3
    4
    5
    function TMyClass.InternalMyFunct: Integer;
    begin
      Result := fMyStrictPrivateField;
      //Result := fMyStrictPrivateField; ne compile pas
    end;
    Pourquoi cet exemple ne fonctionne pas ? on est bien dans la même classe ? c'est parce que la fonction est "public" que l'on n'a pas acces a notre variable "strict private" ?

    Merci
    Bonjour,

    Non : c'est juste que j'ai oublié d'effacer la seconde ligne qui est strictement la même que celle qui précède . J'ai inversé deux fonctions (comme quoi, la rapidité n'est pas toujours l'amie de l'exactitude ) :

    Code :
    1
    2
    3
    4
    5
    function MyThirdClass.MyFunc: Integer;
    begin
      Result := fMyPrivateField;
      //Result := fMyStrictPrivateField; ne compile pas
    end;
    Je corrige sur l'original de ce pas !

    Cordialement.
  • Jipété
    Expert éminent sénior
    Salut,

    remarques plutôt sur la forme que sur le fond, mais bon, je suis comme ça, si la route est mal fichue le trajet va m'être pénible

    Page 4 au milieu, on découvre la déclaration de classe ; tout le monde en a déjà vu, mais il aurait été bon, dans un tuto partant de la base, d'expliquer les mots-clé qu'on y rencontre (private, public, etc.), surtout quand il y a des choses jamais vues par ailleurs, je pense à l'ajout du mot-clé "strict" : c'est quoi la différence entre "private" et "strict private" ? C'est encore plus private que private ?

    Il y a bien une explication succincte en page 11 (en page 11 ! Rappel : je suis en train de lire la page 4...), mais qui n'apporte rien de plus que ce qu'on sait déjà (quand on le sait).
    Ah, la vraie explication se trouve en page 12 : une indication en page 4 serait la bienvenue, àmha.

    Typo page 5 : {$mode objfp} --> {$mode objfpc}

    Page 7 : "préfixée du nom de l'objet qui la convoque, suivi d'un point" ? --> "préfixée du nom de l'objet qui l'invoque, suivie d'un point"

    Il est curieux que la page intitulée "Notes Bas de Page" présente ces fameuses notes toutes regroupées en fin de document, ce qui ne facilite pas la lecture, ni la navigation dans le document : en cliquant sur le n° de la note, on se retrouve à la fin du document mais on n'est pas obligé de se souvenir du fameux n° donc quelle note dois-je lire ? Et en fin de lecture, penser à re-cliquer sur le n° pour retourner où l'on était... Les notes en bas de page sont quand même plus confortables : on ne perd pas son fil.

    Et le truc le plus perturbant, pour moi, est l'absence d'espace, dans 99% des cas, entre le mot-clé en couleur et le mot qui suit. Un exemple parmi tant d'autres, à la 3e ligne de la page 7 :


    Sinon, c'est bien, c'est très bien ! À quand la suite avec les machins virtuels, abstract, etc. ?
  • ThWilliam
    Membre chevronné
    Bonjour !

    Bravo à Gilles pour son tuto.

    J'aurais souhaité cependant que certaines notions soient plus explicites pour un débutant en POO.

    Notamment :

    * l'accès aux propriétés par les "Getters" et "Setters". Dans TAnimal, le setter "SetNom" n'est pas utile, car la procédure ne fait qu'allouer la valeur à FNom. Un setter aurait été utile pour, p.ex., vérifier la valeur assignée en interdisant certaines choses.

    * Le tuto parle très peu du constructeur, ce qui me parait être une notion fondamentale. Contrairement à ce qui est dit, j'utilise toujours un constructeur dans lequel j'initialise les différents champs même si ceux-ci gardent leur valeur par défaut. Habitude prise sous Delphi qui conseillait d'ailleurs vivement d'indiquer aussi la valeur par défaut dans la déclaration de propriété (quand c'est autorisé) :
    property ASoif: Boolean read fASoif write fASoif default false;
    N'oublions pas non plus que, dans beaucoup de cas, un champ de type integer (p.ex) doit être initialisé <> 0.

    Cordialement
    Thierry
  • BeanzMaster
    Expert confirmé
    Bonjour Gilles, article intéressant, juste un petit truc dans la section II-B. Notion de portée

    strict private: l'élément n'est visible que par un élément de la même classe ; --> sauf par transtypage non ?
    private: l'élément n'est visible que par un élément présent dans la même unité;
    --> idem que ci-dessus
    --> et qu'entend tu par élément ???? une procédure de la classe ? ( une fonction hors de la classe, mais dans la même unité n'aura pas accès a ces données, non ? )
    --> "un élément présent dans la même unité" ça ne serait pas plutôt par un élément de la même classe comme "strict"

    strict protected: l'élément n'est utilisable que par un descendant de la classe (donc une classe dérivée) présent dans l'unité ou dans une autre unité que celle de la classe ;
    protected: l'élément n'est utilisable que par un descendant de la classe, qu'il soit dans l'unité de la classe ou dans une autre unité y faisant référence, ou par une autre classe présente dans l'unité de la classe ;

    --> les 2 c'est pareil, non ?

    Il serait le bienvenue de faire un exemple de classe comportant une section "strict private" et "private" pour bien comprendre la différence. La je ne voit pas ce qu"apporte de plus l'utilisation de "strict" et qu'elle est l'avantage de l'utiliser a part faire "jolie"

    Bien à toi et bonne continuation dans tes tutos
    Jérôme
  • BeanzMaster
    Expert confirmé
    Bonjour, merci pour tes explications complémentaires et ta réactivité Gilles, je comprend mieux l'utilité de strict qui est vraiment utile dans beaucoup de cas, faudra maintenant que je pense à mieux structurer mes classes . Juste une chose :

    Code :
    1
    2
    3
    4
    5
    function TMyClass.InternalMyFunct: Integer;
    begin
      Result := fMyStrictPrivateField;
      //Result := fMyStrictPrivateField; ne compile pas
    end;
    Pourquoi cet exemple ne fonctionne pas ? on est bien dans la même classe ? c'est parce que la fonction est "public" que l'on n'a pas acces a notre variable "strict private" ?

    Merci
  • ThWilliam
    Membre chevronné
    Bonsoir Gilles,

    "Ces remarques me plaisent bien. Si tu m'y autorises, je les intégrerais volontiers au texte."
    Of course ! La question ne se pose même pas.
    Et ne te sens pas obligé de me citer parmi les intervenants.

    Thierry
  • gvasseur58
    Responsable Lazarus & Pascal
    Bonjour,

    Merci à toi aussi pour cette lecture attentive
    Envoyé par BeanzMaster

    strict private: l'élément n'est visible que par un élément de la même classe ; --> sauf par transtypage non ?
    private: l'élément n'est visible que par un élément présent dans la même unité;
    --> idem que ci-dessus
    --> et qu'entend tu par élément ???? une procédure de la classe ? ( une fonction hors de la classe, mais dans la même unité n'aura pas accès a ces données, non ? )
    --> "un élément présent dans la même unité" ça ne serait pas plutôt par un élément de la même classe comme "strict"
    Non, pas de transtypage qui tienne ! En revanche, une fonction hors de la classe, mais dans la même unité, aura accès à ces données y compris privées (si elle accède à une instance d'une classe, bien sûr). Donc, je maintiens les définitions.

    Je vais en revanche apporter un exemple pour preuve et te remercie pour cette remarque .

    Voici tout d'abord l'unité d'un projet qui définit mes classes, ainsi qu'une procédure indépendante :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    unit other;
    
    {$mode objfpc}{$H+}
    
    interface
    
    uses
      Classes, SysUtils;
    
    type
    
      { TMyClass }
    
      TMyClass = class
        strict private
          fMyStrictPrivateField: Integer;
        private
          fMyPrivateField: Integer;
        public
          fMyPublicField: Integer;
          function InternalMyFunct: Integer;
      end;
    
      { TMyOtherClass }
    
      TMyOtherClass = class
        public
          ASubClass: TMyClass;
          constructor Create;
          destructor Destroy; override;
      end;
    
      { MyThirdClass }
    
      MyThirdClass = class(TMyClass)
        public
          function MyFunc: Integer;
        end;
    
      procedure MyProc;
    
    implementation
    
    procedure MyProc;
    var
      AnObject: TMyClass;
    begin
      AnObject := TMyClass.Create;
      try
        AnObject.fMyPublicField := 1;
        AnObject.fMyPrivateField := 3;
        //AnObject.fMyStrictPrivateField := 2; ne compile pas
      finally
        AnObject.Free;
      end;
    end;
    
    { MyThirdClass }
    
    function MyThirdClass.MyFunc: Integer;
    begin
      Result := fMyPrivateField;
      //Result := fMyStrictPrivateField; ne compile pas
    end;
    
    { TMyClass }
    
    function TMyClass.InternalMyFunct: Integer;
    begin
      Result := fMyStrictPrivateField;
    end;
    
    { TMyOtherClass }
    
    
    constructor TMyOtherClass.Create;
    begin
      inherited;
      ASubClass := TMyClass.Create;
      ASubClass.fMyPrivateField := 1;
      ASubClass.fMyPrivateField := 4;
      // ASubClass.fMyStrictPrivateField:= 5; ne compile pas
    end;
    
    destructor TMyOtherClass.Destroy;
    begin
      ASubClass.Free;
      inherited Destroy;
    end;
    
    end.
    On voit que les champs privés ne le sont pas vraiment puisqu'ils sont même accessibles par une procédure indépendante qui instancie une classe définie dans l'unité. D'où l'intérêt de strict private qui interdit tout accès, y compris à une classe qui descend de la première.

    A présent, si l'on crée un autre unité faisant référence à celle qui définit les classes, les choses changent, car ce qui est privé n'est plus accessible :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    unit other2;
    
    {$mode objfpc}{$H+}
    
    interface
    
    uses
      Classes, SysUtils, other;
    
    type
    
      { TMyFourthClass }
    
      TMyFourthClass = class(TMyClass)
        public
          procedure MyProc;
      end;
    
    procedure MyProc;
    
    implementation
    
    
    procedure MyProc;
    var
      AnObject: TMyClass;
    begin
      AnObject := TMyClass.Create;
      try
        AnObject.fMyPublicField := 1;
        // AnObject.fMyPrivateField := 3; ne compile pas
        //AnObject.fMyStrictPrivateField := 2; ne compile pas
      finally
        AnObject.Free;
      end;
    end;
    
    { TMyFourthClass }
    
    procedure TMyFourthClass.MyProc;
    begin
      fMyPublicField:= 7;
      //fMyPrivateField := 9; ne compile pas
      //fMyStrictPrivateField := 10; ne compile pas
    end;
    
    
    end.
    J'ai choisi une procédure indépendante et une classe fille. Dans les deux cas, ce qui était accessible dans la même unité ne l'est plus hors de l'unité : le privé est enfin privé !

    Envoyé par BeanzMaster
    strict protected: l'élément n'est utilisable que par un descendant de la classe (donc une classe dérivée) présent dans l'unité ou dans une autre unité que celle de la classe ;
    protected: l'élément n'est utilisable que par un descendant de la classe, qu'il soit dans l'unité de la classe ou dans une autre unité y faisant référence, ou par une autre classe présente dans l'unité de la classe ;
    --> les 2 c'est pareil, non ?
    Pas tout à fait : comme indiqué, avec protected, une autre classe (donc quelconque) qui figure dans l'unité de définition de la classe où apparaît ce protected a accès aux méthodes et aux champs protégés. Avec strict protected, il faut être descendant de la classe pour y avoir accès : une classe non dérivée, même si elle figure dans la même unité que la classe qui comprend le strict protected, n'aura pas accès à ces éléments (champs ou méthodes). Là aussi, un exemple détaillé s'impose sans doute...

    Remarque complémentaire : les détails donnés ici ne concernent pas vraiment les débutants en POO qui utiliseront assez facilement les "classiques" private, protected, public et published et s'abstiendront sans doute d'utiliser les dérivés strict. Ces derniers ne sont pas pour faire "joli" et intéressent tout spécialement les projets assez ambitieux. Je pense par ailleurs que strict private devrait devenir un réflexe tant cette section est utile afin d'éviter les manipulations risquées de champs.

    J'ai conscience de ne pas pouvoir répondre, même en 26 pages, à toutes les questions, mais je promets de proposer des exercices/exemples complémentaires qui illustreront ces problèmes, sans doute dans un tutoriel du genre "Exercices sur la POO". Encore merci pour l'intérêt porté à ce tutoriel perfectible, c'est certain .

    Cordialement,

    Gilles