begin process at 2008 08 29 20:35:31
1 233 885 membres
407 nouveaux aujourd'hui
14 294 membres club

Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum.
Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

[DELPHI] DÉVELOPPER DES CLASSES


Information sur le tutorial

Catégorie :Tutoriaux Date de création : 16/07/2005 16:03:52 Vu : 10 622 fois

Note :
9 / 10 - par 4 personnes
9,00 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

Commentaire sur cette source (8)
Ajouter un commentaire et/ou une note

Description

C'est la mise en route pour passer au développement de composants.

Tutorial

Le tutorial 177 nous laissait en reste sur les classes pour la simple et bonne raison que ça méritait plutôt un beau chapitre complètement dédié. Après avoir lu tout ce qui suit, vous serez prêts pour attaquer le développement de composants (car ce sont des classes). Mais avant, relisez bien le 177 si vous avez quelques problèmes.

J'aurai tendance à équivaloir les mots OBJET et CLASSE. Les classes doivent être créées puis détruites. Pour un objet, c'est pareil : il ne tombe pas du ciel comme çà. De plus, un objet a une forme, des propriétés, un fonctionnement... Bref, on fait des objets ! D'où le nom de programmation orientée objet (POO) qui ne gère presque que des classes.

Allez, on y va...

Contenu
      I) Déclarer une classe
      II) Constuire et détruire l'objet
      III) Déclarer une procédure et une fonction
      IV) Les propriétés
      V) Utiliser sa classe
      VI) Les dangers des publications
      VII) Conclusion

I) Déclarer une classe

Ce n'est pas compliqué. Par défaut, on écrit d'abord ceci :

type
 
TMaClasse = class
 
private
  public
 
protected
 
published
 
end;

On n'oublie pas le "T" de TMaClasse... et on observe 4 rubriques différentes. Qui sont-elles ? Pour avoir de l'aide, cliquez une fois sur les mots en gras et faites F1 (dans Delphi bien sûr ;).

PRIVATE : permet de déclarer des variables, des procédures et des fonctions internes à la classe. De ce fait, aucune classe extérieure (parente ou enfante) ne peut agir sur cette section. Une fois la classe compilée, il n'y a plus aucun recours pour pouvoir modifier cette section (une dérivation de classe ne libère pas le contenu).
PUBLIC : met les variables, les procédures et les fonctions qui y sont déclarées à disposition du développeur et des classes dérivées (on notera plus tard les dangers de cette partie).
PROTECTED : Ce n'est pas très utile... En fait, dans la VCL de Delphi, cela permet de déclarer secrètement des propriétés qui ne peuvent être visibles qui si on dérive la classe et si on publie ce qui est protégé. Les trois autres sections sont plus fondamentales.
PUBLISHED : pour les variables, procédures et fonctions, ça à le même comportement que PUBLIC, mais PUBLISHED sert à indiquer à Delphi quelles propriétés seront visible dans l'inspecteur d'objet dans le cas où vous développez des composants.

Voilà une classe opérationelle la plus simple qui puisse exister. Vous me direz que cette classe n'est en fait pas vide, car Delphi montre haustensiblement qu'un corps de fonctions et de procédures est déjà greffé. Ca, c'est un problème technique géré par Delphi... Sachant qu'on ne peut pas accéder à certains éléments (nom de la classe en format STRING par exemple) par la programmation Pascal, Delphi offre par défaut un package de base autogénéré qui fait passer outre les problèmes. Si Delphi ne faisait pas ça, on serait bien dans la m*****... ;). De plus, ça intègre des fonctionnalités avancées pour la hiérarchisation des classes.

Savoir si une classe est dérivée revient à dire s'il y a un paramètre après CLASS :

type
 
TClassePrimitive = class
 
end;
  TClasseDerivee = class(TComponent)
  end;

 

II) Constuire et détruire l'objet

Toute classe nécessite d'être créée puis détruite (ordre logique). Les autres types du tutorial 177 étaient prêts à l'emploi, mais là, ça ne rigole plus. Il faut un CONSTRUCTOR et un DESTRUCTOR.

Pour l'instant, notre classe n'est pas dérivée. Avec le développement de composants, ce sera bien différent et plus difficile. De ce fait, le constructeur et le destructeur ont une forme totalement libre (je veux dire par là qu'on peut leur ajouter des paramètres). Il faut les considérer comme des procédures très spéciales (repérées par le linker).

unit ClassRoom;
interface
uses
SysUtils;
type
  TMaClasse = class
 
private
   
FVariable : string;
  public
   
constructor Create(Param:string);
    destructor Destroy;
  end;
implementation
constructor TMaClasse.Create(Param:string);
begin
 
FVariable:=Param;
end;
destructor TMaClasse.Destroy;
begin
end;
end.

Le CONSTRUCTOR alloue implicitement de la mémoire et permet d'initialiser les variables internes (déclarées nécessairement dans PRIVATE). J'ajouterai qu'en obligeant la classe à être créée, ça force son initialisation.

Le DESTRUCTOR est appelé lorsque la classe va être détruite et c'est à ce moment qu'il faut faire toutes les libérations de mémoire s'il y en a eu (dans l'ordre inverse des créations). Par exemple, si vous avez utilisé un TPanel (c'est un objet) dans cette classe, il a été créé dans le CONSTRUCTOR, il faudra donc l'éliminer dans le DESTRUCTOR avant que la classe elle-même ne subisse le même traitement. Si on crée Panel1 et Panel2, on supprimera d'abord Panel2 puis Panel1 pour éviter les chevauchements qui sont interdits en Pascal (voir le code JPG de ni69 pour lire un de mes commentaires sur ce sujet).

Il s'avère que seules les classes doivent être libérées. Tous les autres types (string, array, record...) sautent tous seuls... Etant donné que notre exemple ne gère qu'un STRING, il est logique qu'il n'y ait rien dans le DESTRUCTOR. On peut donc effacer le DESTRUCTOR et sa déclaration, car (ici) il est inutile donc superflu. Cependant, il existe toujours. Pour être clair, il existait déjà avant même que vous ne le tapiez vous même. Vous vous souvenez de ma remarque: «Delphi offre par défaut un package de base autogénéré». Là, la classe n'est pas dérivée. Donc, le fait que vous ayez déclaré vous même un DESTRUCTOR ne crée pas de problèmes. L'original sera substitué au nouveau, sachant que de toute manière Delphi vous cachera du code une fois le END du destructeur atteint.

C'est à ce moment que les sérieux du Delphi vont dire: «Mais où est INHERITED ?». Nul part bien sûr... on ne fait pas encore de composants (il n'y a pas eu de dérivation) !! Ca va venir... et OVERRIDE va débouler 8=)

Bref... méditez le morceau de code précédent, puis le suivant :

unit ClassRoom;
interface
uses SysUtils, ExtCtrls;
type
 
TMaClasse = class
 
private
   
FPN : TPanel;
  public
   
constructor Create(Param:string);
    destructor Destroy;
  end;
implementation
constructor
TMaClasse.Create(Param:string);
begin
 
FPN:=TPanel.Create;
end;
destructor TMaClasse.Destroy;
begin
 
FPN.Free;
end;
end.

 

III) Déclarer une procédure et une fonction

C'est toujours un jeu d'enfants. Regardez :

unit ClassRoom;
interface
uses
SysUtils, Dialogs;
type
 
TMaClasse = class
 
private
   
procedure UtilisationInterne(Message:string);
  public
    function
Aleatoire:integer;
  end;
implementation
procedure
TMaClasse.UtilisationInterne;
begin
 
ShowMessage(Message);
end;
function TMaClasse.Aleatoire:integer;
begin
 
Aleatoire:=Random(50);
end;
end.

N'ayant pas de variables à initialiser et étant au sein d'une classe non dérivée (n'oublions aucune hypothèse), je n'ai pas besoin de faire un CONSTRUCTOR, et pas besoin non plus d'un DESTRUCTOR. Par défaut, ils sont cachés.

J'utilise ici la fonction RANDOM qui requiert un appel unique à la fonction RANDOMIZE. Je préfère laisser le programmeur utiliser RANDOMIZE qu'il implementera dans le fichier DPR de son projet. Si j'avais mis RANDOMIZE dans le constructeur, si je dois créer deux variables de type TMaClasse, alors je fais deux CREATE, donc j'appelle deux fois RANDOMIZE ==> je faillis à la règle.

Avez-vous remarqué que sous IMPLEMENTATION, UtilisationInterne n'expose pas son paramètre Message ? Par héritage, ayant écrit procedure TMaClasse.UtilisationInterne, Delphi va rechercher dans PRIVATE les paramètres manquants, ce qui ne crée pas de problèmes. Cependant, par soucis de lisibilité, il est vivement conseillé de mettre le paramétrage en double (quitte à devoir faire plus de modifications), afin de ne pas avoir à jouer de l'ascenseur pour rechercher visuellement ce qu'on a omis d'écrire (ahh, la flaime !... ;)

Vous commencez à sentir le truc...

 

IV) Les propriétés

Ca aussi, c'est ultra méga important pour les composants... Regardez le code suivant (on a un constructeur, car on doit initialiser des variables) :

unit ClassRoom;
interface
uses SysUtils;
type
 
TMaClasse = class
 
private
   
FMessage : string;
  public
    c
onstructor Create;
  published
   
property Message:string read FMessage write FMessage;
    property Message_ReadOnly:string read FMessage;
  end;
implementation
constructor
TMaClasse.Create;
begin
 
FMessage:='';
end;
end.

Le principe est très très simple :
1) On déclare une variable dans PRIVATE avec spécification du type
2) On publie la propriété Message dans PUBLISHED
3) On initialise dans le constructeur

J'ai rajouté Message_ReadOnly pour montrer comment on fait pour avoir une propriété en lecture seule. Par principe inverse, on peut avoir écriture seule.

Comment interpréter le READ et le WRITE de PROPERTY ? C'est un peu comme pour les RECORD :
1)
Objet.Message:='Salut' fait appel au WRITE car on assigne 'Salut' dans la variable FMessage
2)
ShowMessage(Objet.Message) fait appel au READ car on récupère le contenu du message pour l'afficher

Peut-être voudriez vous faire un traitement lors du WRITE ? Sur ce point, les CLASS ont un avantage certain sur les RECORD. Eh bien, vous faîtes comme cela :

unit ClassRoom;
interface
uses
SysUtils;
type
 
TMaClasse = class
 
private
   
FMessage :
string;
   
procedure SetMessage(Value:string);
  public
   
constructor Create;
  published
   
property Message:
string read FMessage write SetMessage;
  end;
implementation
constructor
TMaClasse.Create;
begin
 
FMessage:='';
end;
procedure TMaClasse.
SetMessage(Value:string);
begin
 
if Value<>FMessage then
   
begin
     
FMessage:=Value;
      ShowMessage(FMessage);
    end;
end;
end.

Je vous ai mis un code couleur pour que vous voyiez les liens... Dans cette classe, lorsqu'on modifie (par le WRITE) la propriété Message, le contenu du nouveau message est affiché au programmeur. C'est franchement inutile, mais c'est juste pour illustrer l'effet.

Il est important :
1) De déclarer la procédure SetMessage dans la clause PRIVATE
2) De nommer la procédure SetQuelqueChose avec un "SET" au début (bonnes habitudes obligent)
3) De ne mettre qu'un seul paramètre nommé Value (idem) du même type que la propriété (couleur rouge)

On doit même pouvoir faire pareil dans l'autre sens avec le READ. Il suffit alors de transformer procedure SetMessage(Value:string) en function GetMessage:string avec cette fois un "GET" au début. On aurait alors :

procedure TMaClasse.GetMessage:string;
begin
 
GetMessage:=FMessage;
  ShowMessage(FMessage);
end;

!!!!!! ATTENTION !!!!!!
Au sein d'une même classe, utilisez ABSOLUMENT les variables F* pour récupérer le contenu des propriétés. L'exemple précédent est correct. Le suivant est interdit :

procedure TMaClasse.GetMessage:string;
begin
 
GetMessage:=FMessage;
  ShowMessage(
Message);
end;

Cet appel à Message enchaîne un READ, donc un GetMessage et on aurait un équivalent en référence circulaire (une boucle sans fin quoi) :

procedure TMaClasse.GetMessage:string;
begin
 
GetMessage:=FMessage;
//ici on assigne le résultat de sortie (no problemo)
 
ShowMessage(GetMessage);
end;

Utilisez TOUJOURS les variables privées et pas les publications !

 

V) Utiliser sa classe

On a créé un fichier CLASSROOM.PAS qui contient ceci :

unit ClassRoom;
interface
uses SysUtils;
type
 
TMaClasse = class
 
private
   
FMessage : string;
  public
   
constructor Create;
  published
    property
Message:string read FMessage write FMessage;
  end;
implementation
constructor
TMaClasse.Create;
begin
 
FMessage:='';
end;
end.

Vous avez un nouveau projet, donc un fichier UNIT1.PAS.

Si vous gérez une variable globale (non cantonnée à une procédure), vous avez ceci (ce qui est en vert doit être rajouté). La variable Clss est prête à l'emploi et sera détruite automatiquement lorsque l'application se fermera.

unit Unit1;
interface
uses
Windows, SysUtils, Classes, Controls, Forms, Dialogs,
ClassRoom;
type
 
TForm1 = class(TForm)
  private
   
{ Déclarations privées }
  public
   
{ Déclarations publiques }
  end;
var Form1 : TForm1;
   
Clss : TMaClasse;
implementation
{$R *.DFM}
initialization
  Clss:=TMaClasse.Create;
finalization
 
Clss.Free;
end.

Si vous gérez un évènement OnClick sur un bouton, alors vous obtenez plutôt ceci :

unit Unit1;
interface
uses
Windows, SysUtils, Classes, Controls, Forms, Dialogs,
ClassRoom;
type
 
TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
   
{ Déclarations privées }
  public
   
{ Déclarations publiques }
  end;
var Form1 : TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var Clss : TMaClasse;
begin
 
try
   
[...]
  finally
   
Clss.Free;
  end;
end;
end.

Dans ces cas extrêmes, il faut toujours faire un TRY...FINALLY...END afin d'être sûr que l'objet sera libéré. Si on avait pas le TRY, si lors du [...] il se produit une exception, alors le FREE ne serait pas appelé et l'objet resterait en mémoire. C'est pas très propre...

Pour ce qui concerne l'affrontement entre ces deux codes, il est préférable de faire une variable globale :
1) elle n'est créée qu'une seule fois, ce qui limite les bribes en mémoire si le Free n'est pas complet à 100% (avec TBitmap ça défonce et rien que pour ça, je lui met 0/20)
2) on a un objet à tout faire (le risque étant que vous vous emméliez les pinceaux, mais qui est limité complètement dès que vous avez une vision très bonne du fonctionnement de la classe).

 

VI) Les dangers des publications

Les classes encapsulent de nombreux traitements mais ce n'est pas pour autant un cocon hermétique intouchable. Il faut donc offrir au développeur le strict nécessaire afin qu'il puisse puiser le maximum de ressource dans la classe sans altérer son fonctionnement.

PUBLIC est dans le colimateur... Quoi donner et quoi ne pas donner ? Il faut déjà un peu de jugeotte !

Imaginons une classe comporte en elle-même une procédure de "formatage de disque" [c'est complètement idiot, mais admettons]. Il est clair que cette procédure ne doit pas être publiée. Préférez plutôt publier une fonction dont la tâche serait de déclencher ce hara-kiri selon certaines modalités. C'est comme les jeunes bambins, il faut les encadrer : on laisse libre, mais on ne veut pas le bazar.

Il faut réfléchir sur ce dont on a besoin, et sur ce qu'on met à disposition du développeur.

 

VII) Conclusion

En mixant un peu le tout, on arrive à faire des choses sympathiques.

Tanguy ALTERT, http://altert.family.free.fr/

  • signaler à un administrateur
    Commentaire de rhabib le 25/12/2005 20:51:34

    bonsoir , je trouve que votre tuto est tres bien sauf que dans mon cas , un débutant ,  j'ai lu :

    I) Déclarer une classe

    Ce n'est pas compliqué. Par défaut, on écrit d'abord ceci :

    type
      TMaClasse = class
      private
      public
      protected
      published
      end;

    ===> le probléme est que je n'ai pas su dans qu'el partie de la form mettre ce code ? merci de m'aider , voici le code de mon nouveau projet :

    unit Unit1;

    interface

    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs;

    type
      TForm1 = class(TForm)
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    type
      tform2 = CLASS
      private

      end;
    var
      Form1: TForm1;

    implementation

    {$R *.dfm}

    end.

  • signaler à un administrateur
    Commentaire de PoulpHunter le 23/02/2006 02:40:11

    Comme sa :

    unit Unit1;

    interface

    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs;

    type
      TMaClasse = class
      private
      public
      protected
      published
      end;
      TForm1 = class(TForm)
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    type
      tform2 = CLASS
      private

      end;
    var
      Form1: TForm1;

    implementation

    {$R *.dfm}

    end.

  • signaler à un administrateur
    Commentaire de rhabib le 23/02/2006 08:43:49

    merci beaucoup de votre reponse , c'est tres clair .

  • signaler à un administrateur
    Commentaire de douanka le 04/06/2006 03:34:25

    je trouve ce tuto super et je dois reconnaitre qu'il m'a beaucoup servi... Cependant il comporte une abération....
    En effet, contrairement a se qui est marqué, la clause 'protected' peut s'avérer trés utile et resoudre certains problèmes de programmation... Voila je pense sincerement que la remarque devait etre faite... Mais sinon encore merci pour ton tuto qui est vraiment simple a comprendre et qui est l'un des rare sur le web ds la langue de Moliere... bne prog!

  • signaler à un administrateur
    Commentaire de sky7rip le 27/08/2006 12:04:48

    Génial ! Merci beaucoup :)

  • signaler à un administrateur
    Commentaire de Pollux84 le 28/08/2006 21:18:10

    CooOL tuto trés claire, simple, bien expliquer :)
    merci

  • signaler à un administrateur
    Commentaire de Toya78 le 08/10/2006 11:48:21

    Merci pour le tuto !
    J'ai tout de même une question à propos de la libération de la mémoire :

    Ma classe a comme attibuts un tableau (dynamique) de Packed Record.

    En gros :

    -------------------------------------------------
    TMonRecord = Packed Record
       SL : TStringList;
    end;

    TMaClasse = class
       private
          TabRec : Array of TMonRecord;
       ........
    -------------------------------------------------

    Jusque là aucun problème puisque d'après le tuto les tableaux et les Packed Record sont détruits automatiquement.

    Seul problème : mon Packed Record contient, lui, un gros TStringList.

    Ma classe utilise donc pas mal de mémoire qui est mal libérée et je ne vois pas d'où ça peut venir (puisqu'apparemment tout est automatiquement détruit).

    Une idée ?

  • signaler à un administrateur
    Commentaire de cirec le 06/03/2007 15:15:36 administrateur CS

    C'est même un tableau de TStringList

    Si ça t'interesse toujours ...
    il faut prendre en charge la libération de la mémoire dans le Destructeur de TMaClass

    Destructor TMaClasse.Destroy;
    Var I : Integer;
    Begin
      For I := Low(TabRec) To High(TabRec) Do TabRec[I].SL.Free;
      Inherited;
    End;

Ajouter un commentaire

Pub



Appels d'offres

Recherche developpeur ...
Budget : 700€
SITE MARCHAND LOCATION...
Budget : 3 000€
SITE MARCHAND POUR HOTEL
Budget : 4 000€

CalendriCode

Août 2008
LMMJVSD
    123
45678910
11121314151617
18192021222324
25262728293031

Boutique

Boutique de goodies CodeS-SourceS